From: Stuart Prescott Date: Sun, 6 Oct 2024 04:08:12 +0000 (+1100) Subject: New upstream version 6.7.2 X-Git-Tag: archive/raspbian/6.10.3-2+rpi1~1^2~78^2 X-Git-Url: https://dgit.raspbian.org/%22http://www.example.com/cgi/%22/%22http:/www.example.com/cgi/%22?a=commitdiff_plain;h=fe0af22672d31dc4b43bcc60fdd32d86819e35b1;p=pyside6.git New upstream version 6.7.2 --- diff --git a/.flake8 b/.flake8 index ef866035..f7b40d74 100644 --- a/.flake8 +++ b/.flake8 @@ -5,5 +5,8 @@ exclude = rc_*.py,*_rc.py,ui_*.py per-file-ignores = # for init_test_paths() hack *_test_*.py:E402 + *_test.py:E402 + *bug_*.py:E402 + test_*.py:E402 + signal_across_threads.py:E402 __init__.py:F401,E402 - diff --git a/LICENSES/Apache-2.0.txt b/LICENSES/Apache-2.0.txt new file mode 100644 index 00000000..136d9004 --- /dev/null +++ b/LICENSES/Apache-2.0.txt @@ -0,0 +1,61 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + + "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/README.md b/README.md index cdeb0398..35f5a906 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,7 @@ using `setup.py build`: ## Requirements - * Python 3.8+ is supported (for Qt 6.6+) + * Python 3.9+ is supported (for Qt 6.7+) * CMake: Specify the path to cmake with `--cmake` option or add cmake to the system path. * Qt 6.x is supported. Specify the path to qtpaths with `--qtpaths` option or diff --git a/README.pyside6.md b/README.pyside6.md index 8c70b1c8..9178660d 100644 --- a/README.pyside6.md +++ b/README.pyside6.md @@ -88,7 +88,7 @@ and [join our community](https://wiki.qt.io/Qt_for_Python#Community)! ### Licensing -PySide6 is available under both Open Source (LGPLv3/GPLv2) and commercial +PySide6 is available under both Open Source (LGPLv3/GPLv3) and commercial license. Using PyPi is the recommended installation source, because the content of the wheels is valid for both cases. For more information, refer to the [Qt Licensing page](https://www.qt.io/licensing/). diff --git a/README.pyside6_addons.md b/README.pyside6_addons.md index 3247a550..e6044c4a 100644 --- a/README.pyside6_addons.md +++ b/README.pyside6_addons.md @@ -65,7 +65,7 @@ and [join our community](https://wiki.qt.io/Qt_for_Python#Community)! ### Licensing -PySide6 is available under both Open Source (LGPLv3/GPLv2) and commercial +PySide6 is available under both Open Source (LGPLv3/GPLv3) and commercial license. Using PyPi is the recommended installation source, because the content of the wheels is valid for both cases. For more information, refer to the [Qt Licensing page](https://www.qt.io/licensing/). diff --git a/README.pyside6_essentials.md b/README.pyside6_essentials.md index b23ce890..7f96c19b 100644 --- a/README.pyside6_essentials.md +++ b/README.pyside6_essentials.md @@ -26,6 +26,7 @@ it includes only the essentials Qt modules: * QtQml * QtQuick * QtQuickControls2 +* QtQuickTest * QtQuickWidgets * QtXml * QtTest @@ -50,7 +51,7 @@ and [join our community](https://wiki.qt.io/Qt_for_Python#Community)! ### Licensing -PySide6 is available under both Open Source (LGPLv3/GPLv2) and commercial +PySide6 is available under both Open Source (LGPLv3/GPLv3) and commercial license. Using PyPi is the recommended installation source, because the content of the wheels is valid for both cases. For more information, refer to the [Qt Licensing page](https://www.qt.io/licensing/). diff --git a/README.pyside6_examples.md b/README.pyside6_examples.md index ffa7d83d..15e31815 100644 --- a/README.pyside6_examples.md +++ b/README.pyside6_examples.md @@ -28,7 +28,7 @@ and [join our community](https://wiki.qt.io/Qt_for_Python#Community)! ### Licensing -PySide6 is available under both Open Source (LGPLv3/GPLv2) and commercial +PySide6 is available under both Open Source (LGPLv3/GPLv3) and commercial licenses. Using PyPi is the recommended installation source, because the content of the wheels is valid for both cases. For more information, refer to the [Qt Licensing page](https://www.qt.io/licensing/). diff --git a/build_scripts/__init__.py b/build_scripts/__init__.py index de28b82b..128bb239 100644 --- a/build_scripts/__init__.py +++ b/build_scripts/__init__.py @@ -17,7 +17,10 @@ PYSIDE_UNIX_BIN_TOOLS = ["lupdate", "lrelease", "qmllint", "qmlformat", - "qmlls"] + "qmlls", + "qsb", + "balsam", + "balsamui"] # tools that are bundled as .app in macOS, but are normal executables in Linux and Windows PYSIDE_UNIX_BUNDLED_TOOLS = ["assistant", diff --git a/build_scripts/build_info_collector.py b/build_scripts/build_info_collector.py index a073fb70..30ce187c 100644 --- a/build_scripts/build_info_collector.py +++ b/build_scripts/build_info_collector.py @@ -104,7 +104,7 @@ def _get_py_library_unix(build_type, py_version, py_prefix, py_libdir, if hasattr(sys, "pypy_version_info"): vi = sys.version_info[:2] version_quirk = ".".join(map(str, vi)) if vi >= (3, 9) else "3" - pypy_libdir = Path(py_libdir).parent / "bin" + pypy_libdir = Path(py_libdir).parent / "bin" for lib_ext in lib_exts: lib_name = f"libpypy{version_quirk}-c{lib_ext}" pypy_library = pypy_libdir / lib_name diff --git a/build_scripts/config.py b/build_scripts/config.py index 5a711f44..0a6eebf7 100644 --- a/build_scripts/config.py +++ b/build_scripts/config.py @@ -61,7 +61,6 @@ class Config(object): self.python_version_classifiers = [ 'Programming Language :: Python', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', @@ -116,7 +115,7 @@ class Config(object): setup_kwargs['zip_safe'] = False setup_kwargs['cmdclass'] = cmd_class_dict setup_kwargs['version'] = package_version - setup_kwargs['python_requires'] = ">=3.8, <3.13" + setup_kwargs['python_requires'] = ">=3.9, <3.13" if log_level == LogLevel.QUIET: # Tells setuptools to be quiet, and only print warnings or errors. diff --git a/build_scripts/log.py b/build_scripts/log.py index 16e4cca2..c9ccf3fb 100644 --- a/build_scripts/log.py +++ b/build_scripts/log.py @@ -8,8 +8,8 @@ from enum import Enum logging.basicConfig(format="[%(levelname)s]: %(message)s", level=logging.INFO) log = logging.getLogger("qtforpython") + class LogLevel(Enum): QUIET = 1 INFO = 2 VERBOSE = 3 - diff --git a/build_scripts/main.py b/build_scripts/main.py index cc77c40b..bf71f996 100644 --- a/build_scripts/main.py +++ b/build_scripts/main.py @@ -10,7 +10,7 @@ import sysconfig import time from packaging.version import parse as parse_version from pathlib import Path -from shutil import copytree +from shutil import copytree, rmtree from textwrap import dedent # PYSIDE-1760: Pre-load setuptools modules early to avoid racing conditions. @@ -690,7 +690,7 @@ class PysideBuild(_build, CommandMixin, BuildInfoCollectorMixin): else: raise SetupError("option limited-api must be 'yes' or 'no' " "(default yes if applicable, i.e. Python " - "version >= 3.8 and release build if on Windows)") + "version >= 3.9 and release build if on Windows)") if OPTION["DISABLE_PYI"]: cmake_cmd.append("-DDISABLE_PYI=yes") @@ -741,7 +741,7 @@ class PysideBuild(_build, CommandMixin, BuildInfoCollectorMixin): cmake_cmd.append(f"-DPACKAGE_SETUP_PY_PACKAGE_TIMESTAMP={timestamp}") if extension.lower() in [SHIBOKEN]: - cmake_cmd.append("-DUSE_PYTHON_VERSION=3.8") + cmake_cmd.append("-DUSE_PYTHON_VERSION=3.9") cmake_cmd += platform_cmake_options() @@ -783,7 +783,7 @@ class PysideBuild(_build, CommandMixin, BuildInfoCollectorMixin): os.environ['MACOSX_DEPLOYMENT_TARGET'] = deployment_target if OPTION["BUILD_DOCS"]: - # Build the whole documentation (rst + API) by default + # Build the whole documentation (Base + API) by default cmake_cmd.append("-DFULLDOCSBUILD=1") if OPTION["DOC_BUILD_ONLINE"]: @@ -1195,17 +1195,21 @@ class PysideBuild(_build, CommandMixin, BuildInfoCollectorMixin): log.debug(f"Patched rpath to '{rpath_value}' in {library}.") -class PysideRstDocs(Command, CommandMixin): - description = "Build .rst documentation only" +class PysideBaseDocs(Command, CommandMixin): + description = "Build the base documentation only" user_options = CommandMixin.mixin_user_options def __init__(self, *args, **kwargs): - self.command_name = "build_rst_docs" + if args[0].commands[0] == "build_rst_docs": + args[0].commands[0] = "build_base_docs" + log.warning("'build_rst_docs' is deprecated and will be removed. " + "Please use 'build_base_docs' instead.") + self.command_name = "build_base_docs" Command.__init__(self, *args, **kwargs) CommandMixin.__init__(self) def initialize_options(self): - log.info("-- This build process will not include the API documentation." + log.info("-- This build process will not include the API documentation. " "API documentation requires a full build of pyside/shiboken.") self.skip = False if config.is_internal_shiboken_generator_build(): @@ -1215,8 +1219,13 @@ class PysideRstDocs(Command, CommandMixin): self.doc_dir = config.setup_script_dir / "sources" / self.name / "doc" # Check if sphinx is installed to proceed. found = importlib.util.find_spec("sphinx") + self.html_dir = Path("html") if found: if self.name == SHIBOKEN: + # Delete the 'html' directory since new docs will be generated anyway + if self.html_dir.is_dir(): + rmtree(self.html_dir) + log.info("-- Deleted old html directory") log.info("-- Generating Shiboken documentation") log.info(f"-- Documentation directory: 'html/{PYSIDE}/{SHIBOKEN}/'") elif self.name == PYSIDE: @@ -1224,7 +1233,6 @@ class PysideRstDocs(Command, CommandMixin): log.info(f"-- Documentation directory: 'html/{PYSIDE}/'") else: raise SetupError("Sphinx not found - aborting") - self.html_dir = Path("html") # creating directories html/pyside6/shiboken6 try: @@ -1272,7 +1280,7 @@ class PysideRstDocs(Command, CommandMixin): raise SetupError(f"Error running CMake for {self.doc_dir}") if self.name == PYSIDE: - self.sphinx_src = self.out_dir / "rst" + self.sphinx_src = self.out_dir / "base" example_gallery = config.setup_script_dir / "tools" / "example_gallery" / "main.py" assert example_gallery.is_file() example_gallery_cmd = [sys.executable, os.fspath(example_gallery)] @@ -1307,7 +1315,9 @@ cmd_class_dict = { 'develop': PysideDevelop, 'install': PysideInstall, 'install_lib': PysideInstallLib, - 'build_rst_docs': PysideRstDocs, + 'build_base_docs': PysideBaseDocs, + # TODO: Remove build_rst_docs in the next version, see PYSIDE-2504 + 'build_rst_docs': PysideBaseDocs, } if wheel_module_exists: pyside_bdist_wheel = get_bdist_wheel_override() diff --git a/build_scripts/options.py b/build_scripts/options.py index 6c6acd58..bfaf0326 100644 --- a/build_scripts/options.py +++ b/build_scripts/options.py @@ -123,7 +123,8 @@ def _jobs_option_value(): def find_qtpaths(): # for these command --qtpaths should not be required - no_qtpaths_commands = ["--help", "--help-commands", "--qt-target-path", "build_rst_docs"] + no_qtpaths_commands = ["--help", "--help-commands", "--qt-target-path", "build_base_docs", + "build_rst_docs"] for no_qtpaths_command in no_qtpaths_commands: if any(no_qtpaths_command in argument for argument in sys.argv): @@ -244,7 +245,8 @@ class CommandMixin(object): # We redeclare plat-name as an option so it's recognized by the # install command and doesn't throw an error. ('plat-name=', None, 'The platform name for which we are cross-compiling'), - ('unity', None, 'Use CMake UNITY_BUILD_MODE'), + ('unity', None, 'Use CMake UNITY_BUILD_MODE (obsolete)'), + ('no-unity', None, 'Disable CMake UNITY_BUILD_MODE'), ('unity-build-batch-size=', None, 'Value of CMAKE_UNITY_BUILD_BATCH_SIZE') ] @@ -305,6 +307,7 @@ class CommandMixin(object): self.internal_cmake_install_dir_query_file_path = None self._per_command_mixin_options_finalized = False self.unity = False + self.no_unity = False self.unity_build_batch_size = "16" # When initializing a command other than the main one (so the @@ -369,7 +372,8 @@ class CommandMixin(object): OPTION['NO_STRIP'] = self.no_strip OPTION['ONLYPACKAGE'] = self.only_package OPTION['STANDALONE'] = self.standalone - OPTION['IGNOREGIT'] = self.ignore_git + if self.ignore_git: + _warn_deprecated_option('ignore_git') OPTION['SKIP_DOCS'] = self.skip_docs OPTION['BUILD_DOCS'] = self.build_docs OPTION['BUILDTESTS'] = self.build_tests @@ -392,8 +396,8 @@ class CommandMixin(object): # By default they are False, so we check if they changed with xor if bool(OPTION["QUIET"]) != bool(OPTION["VERBOSE_BUILD"]): - log.warn("Using --quiet and --verbose-build is deprecated. " - "Please use --log-level=quiet or --log-level=verbose instead.") + log.warning("Using --quiet and --verbose-build is deprecated. " + "Please use --log-level=quiet or --log-level=verbose instead.") # We assign a string value instead of the enum # because is what we get from the command line. # Later we assign the enum @@ -420,7 +424,10 @@ class CommandMixin(object): OPTION['SANITIZE_ADDRESS'] = self.sanitize_address OPTION['SHORTER_PATHS'] = self.shorter_paths OPTION['DOC_BUILD_ONLINE'] = self.doc_build_online - OPTION['UNITY'] = self.unity + if self.unity: + log.warning("Using --unity no longer has any effect, " + "Unity build mode is now the default.") + OPTION['UNITY'] = not self.no_unity OPTION['UNITY_BUILD_BATCH_SIZE'] = self.unity_build_batch_size qtpaths_abs_path = None @@ -454,7 +461,7 @@ class CommandMixin(object): qt_target_path=qt_target_path, cmake_toolchain_file=cmake_toolchain_file) - if 'build_rst_docs' not in sys.argv: + if 'build_base_docs' not in sys.argv and 'build_rst_docs' not in sys.argv: try: QtInfo().prefix_dir except Exception as e: @@ -542,8 +549,10 @@ class CommandMixin(object): # We also don't do auto-searching if qt-target-path is passed # explicitly. This is to help with the building of host tools # while cross-compiling. - # Skip this process for the 'build_rst_docs' command - if not (self.is_cross_compile and not self.qt_target_path + # Skip this process for the 'build_base_docs' command + if (not self.is_cross_compile + and not self.qt_target_path + and 'build_base_docs' not in sys.argv and 'build_rst_docs' not in sys.argv): # Enforce usage of qmake in QtInfo if it was given explicitly. if self.qmake: diff --git a/build_scripts/platforms/linux.py b/build_scripts/platforms/linux.py index cb388e8a..b4c66d94 100644 --- a/build_scripts/platforms/linux.py +++ b/build_scripts/platforms/linux.py @@ -95,7 +95,7 @@ def prepare_standalone_package_linux(pyside_build, _vars, cross_build=False, is_ _filter=["*.so"], recursive=True, _vars=_vars) - if not is_pypy: + if not is_pypy and not is_android: copydir("{install_dir}/plugins/designer", plugins_target / "designer", _filter=["*.so"], diff --git a/build_scripts/platforms/unix.py b/build_scripts/platforms/unix.py index 17c4ba34..3333f5f9 100644 --- a/build_scripts/platforms/unix.py +++ b/build_scripts/platforms/unix.py @@ -122,7 +122,7 @@ def prepare_packages_posix(pyside_build, _vars, cross_build=False): scripts = ["pyside_tool.py", "metaobjectdump.py", "project.py", "qml.py", "qtpy2cpp.py", "deploy.py"] - script_dirs = ["qtpy2cpp_lib", "deploy_lib", "project"] + script_dirs = ["qtpy2cpp_lib", "deploy_lib", "project"] if sys.platform.startswith("linux"): scripts.append("android_deploy.py") @@ -170,10 +170,10 @@ def prepare_packages_posix(pyside_build, _vars, cross_build=False): lib_exec_filters.append('QtWebEngineProcess') if lib_exec_filters: libexec_executables.extend(copydir("{qt_lib_execs_dir}", - destination_qt_dir / "libexec", - _filter=lib_exec_filters, - recursive=False, - _vars=_vars)) + destination_qt_dir / "libexec", + _filter=lib_exec_filters, + recursive=False, + _vars=_vars)) # /lib/lib* -> {st_package_name}/ copydir( @@ -214,7 +214,7 @@ def prepare_packages_posix(pyside_build, _vars, cross_build=False): # /pyside6/{st_package_name}/QtAsyncio/* -> # /{st_package_name}/QtAsyncio/* copydir( - f"{{site_packages_dir}}/{{st_package_name}}/QtAsyncio", + "{site_packages_dir}/{st_package_name}/QtAsyncio", "{st_build_dir}/{st_package_name}/QtAsyncio", _vars=_vars) diff --git a/build_scripts/platforms/windows_desktop.py b/build_scripts/platforms/windows_desktop.py index c3b19d5a..9c29953b 100644 --- a/build_scripts/platforms/windows_desktop.py +++ b/build_scripts/platforms/windows_desktop.py @@ -167,7 +167,7 @@ def prepare_packages_win32(pyside_build, _vars): # /pyside6/{st_package_name}/QtAsyncio/* -> # /{st_package_name}/QtAsyncio/* copydir( - f"{{site_packages_dir}}/{{st_package_name}}/QtAsyncio", + "{site_packages_dir}/{st_package_name}/QtAsyncio", "{st_build_dir}/{st_package_name}/QtAsyncio", _vars=_vars) @@ -288,6 +288,11 @@ def copy_qt_artifacts(pyside_build, destination_qt_dir, copy_pdbs, _vars): # /bin/*.dll and Qt *.exe -> /{st_package_name} qt_artifacts_permanent = [ + "avcodec-60.dll", + "avformat-60.dll", + "avutil-58.dll", + "swresample-4.dll", + "swscale-7.dll", "opengl*.dll", "designer.exe", "linguist.exe", diff --git a/build_scripts/qfp_tool.py b/build_scripts/qfp_tool.py new file mode 100644 index 00000000..abaf48fc --- /dev/null +++ b/build_scripts/qfp_tool.py @@ -0,0 +1,457 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +import datetime +import os +import re +import subprocess +import sys +import time +import warnings +from argparse import ArgumentParser, RawTextHelpFormatter +from enum import Enum, auto +from pathlib import Path +from typing import List + +DESC = """ +Utility script for working with Qt for Python. + +Feel free to extend! + +Typical Usage: +Update and build a repository: python qfp_tool -p -b + +qfp_tool.py uses a configuration file "%CONFIGFILE%" +in the format key=value. + +It is possible to use repository-specific values by adding a key postfixed by +a dash and the repository folder base name, eg: +Modules-pyside-setup512=Core,Gui,Widgets,Network,Test + +Configuration keys: +Acceleration Incredibuild or unset +BuildArguments Arguments to setup.py +Generator Generator to be used for CMake. Currently, only Ninja is + supported. +Jobs Number of jobs to be run simultaneously +Modules Comma separated list of modules to be built + (for --module-subset=) +Python Python executable (Use python_d for debug builds on Windows) + +Arbitrary keys can be defined and referenced by $(name): + +MinimalModules=Core,Gui,Widgets,Network,Test +Modules=$(MinimalModules),Multimedia +Modules-pyside-setup-minimal=$(MinimalModules) +""" + + +class Acceleration(Enum): + NONE = 0 + INCREDIBUILD = 1 + + +class BuildMode(Enum): + NONE = 0 + BUILD = 1 + RECONFIGURE = 2 + MAKE = 3 + + +class UnityMode(Enum): + DEFAULT = auto() + ENABLE = auto() + DISABLE = auto() + + +DISABLE_UNITY_OPTION = "--no-unity" +LOG_LEVEL_OPTION = "--log-level" +DEFAULT_BUILD_ARGS = ['--build-tests', '--skip-docs', LOG_LEVEL_OPTION, "quiet"] +IS_WINDOWS = sys.platform == 'win32' +INCREDIBUILD_CONSOLE = 'BuildConsole' if IS_WINDOWS else '/opt/incredibuild/bin/ib_console' +# Config file keys +ACCELERATION_KEY = 'Acceleration' +BUILDARGUMENTS_KEY = 'BuildArguments' +GENERATOR_KEY = 'Generator' +JOBS_KEY = 'Jobs' +MODULES_KEY = 'Modules' +PYTHON_KEY = 'Python' + +DEFAULT_MODULES = "Core,Gui,Widgets,Network,Test,Qml,Quick,Multimedia,MultimediaWidgets" +DEFAULT_CONFIG_FILE = f"Modules={DEFAULT_MODULES}\n" + +build_mode = BuildMode.NONE +opt_dry_run = False +opt_verbose = False +opt_unity_mode = UnityMode.DEFAULT + + +def which(needle: str): + """Perform a path search""" + needles = [needle] + if IS_WINDOWS: + for ext in ("exe", "bat", "cmd"): + needles.append(f"{needle}.{ext}") + + for path in os.environ.get("PATH", "").split(os.pathsep): + for n in needles: + binary = Path(path) / n + if binary.is_file(): + return binary + return None + + +def command_log_string(args: List[str], directory: Path): + result = f'[{directory.name}]' + for arg in args: + result += f' "{arg}"' if ' ' in arg else f' {arg}' + return result + + +def execute(args: List[str]): + """Execute a command and print to log""" + log_string = command_log_string(args, Path.cwd()) + print(log_string) + if opt_dry_run: + return + exit_code = subprocess.call(args) + if exit_code != 0: + raise RuntimeError(f'FAIL({exit_code}): {log_string}') + + +def run_process_output(args): + """Run a process and return its output. Also run in dry_run mode""" + std_out = subprocess.Popen(args, universal_newlines=1, + stdout=subprocess.PIPE).stdout + result = [line.rstrip() for line in std_out.readlines()] + std_out.close() + return result + + +def run_git(args): + """Run git in the current directory and its submodules""" + args.insert(0, git) # run in repo + execute(args) # run for submodules + + +def expand_reference(cache_dict, value): + """Expand references to other keys in config files $(name) by value.""" + pattern = re.compile(r"\$\([^)]+\)") + while True: + match = pattern.match(value) + if not match: + break + key = match.group(0)[2:-1] + value = value[:match.start(0)] + cache_dict[key] + value[match.end(0):] + return value + + +def editor(): + editor = os.getenv('EDITOR') + if not editor: + return 'notepad' if IS_WINDOWS else 'vi' + editor = editor.strip() + if IS_WINDOWS: + # Windows: git requires quotes in the variable + if editor.startswith('"') and editor.endswith('"'): + editor = editor[1:-1] + editor = editor.replace('/', '\\') + return editor + + +def edit_config_file(): + exit_code = -1 + try: + exit_code = subprocess.call([editor(), config_file]) + except Exception as e: + reason = str(e) + print(f'Unable to launch: {editor()}: {reason}') + return exit_code + + +""" +Config file handling, cache and read function +""" +config_dict = {} + + +def read_config_file(file_name): + """Read the config file into config_dict, expanding continuation lines""" + global config_dict + keyPattern = re.compile(r'^\s*([A-Za-z0-9\_\-]+)\s*=\s*(.*)$') + with open(file_name) as f: + while True: + line = f.readline() + if not line: + break + line = line.rstrip() + match = keyPattern.match(line) + if match: + key = match.group(1) + value = match.group(2) + while value.endswith('\\'): + value = value.rstrip('\\') + value += f.readline().rstrip() + config_dict[key] = expand_reference(config_dict, value) + + +def read_config(key): + """ + Read a value from the '$HOME/.qfp_tool' configuration file. When given + a key 'key' for the repository directory '/foo/qt-5', check for the + repo-specific value 'key-qt5' and then for the general 'key'. + """ + if not config_dict: + read_config_file(config_file) + repo_value = config_dict.get(f"{key}-{base_dir}") + return repo_value if repo_value else config_dict.get(key) + + +def read_bool_config(key): + value = read_config(key) + return value and value in ['1', 'true', 'True'] + + +def read_int_config(key, default=-1): + value = read_config(key) + return int(value) if value else default + + +def read_acceleration_config(): + value = read_config(ACCELERATION_KEY) + if value: + value = value.lower() + if value == 'incredibuild': + return Acceleration.INCREDIBUILD + return Acceleration.NONE + + +def read_config_build_arguments(): + value = read_config(BUILDARGUMENTS_KEY) + if value: + return re.split(r'\s+', value) + return DEFAULT_BUILD_ARGS + + +def read_config_modules_argument(): + value = read_config(MODULES_KEY) + if value and value != '' and value != 'all': + return f"--module-subset={value}" + return None + + +def read_config_python_binary() -> str: + binary = read_config(PYTHON_KEY) + virtual_env = os.environ.get('VIRTUAL_ENV') + if not binary: + # Use 'python3' unless virtualenv is set + use_py3 = not virtual_env and which('python3') + binary = 'python3' if use_py3 else 'python' + binary = Path(binary) + if not binary.is_absolute(): + abs_path = which(str(binary)) + if abs_path: + binary = abs_path + else: + warnings.warn(f'Unable to find "{binary}"', RuntimeWarning) + if virtual_env: + if not str(binary).startswith(virtual_env): + w = f'Python "{binary}" is not under VIRTUAL_ENV "{virtual_env}"' + warnings.warn(w, RuntimeWarning) + return str(binary) + + +def get_config_file(base_name) -> Path: + global user + home = os.getenv('HOME') + if IS_WINDOWS: + # Set a HOME variable on Windows such that scp. etc. + # feel at home (locating .ssh). + if not home: + home = os.getenv('HOMEDRIVE') + os.getenv('HOMEPATH') + os.environ['HOME'] = home + user = os.getenv('USERNAME') + config_file = Path(os.getenv('APPDATA')) / base_name + else: + user = os.getenv('USER') + config_dir = Path(home) / '.config' + if config_dir.exists(): + config_file = config_dir / base_name + else: + config_file = Path(home) / f".{base_name}" + return config_file + + +def build(target: str): + """Run configure and build steps""" + start_time = time.time() + + arguments = [] + acceleration = read_acceleration_config() + if not IS_WINDOWS and acceleration == Acceleration.INCREDIBUILD: + arguments.append(INCREDIBUILD_CONSOLE) + arguments.appendh('--avoid') # caching, v0.96.74 + arguments.extend([read_config_python_binary(), 'setup.py', target]) + build_arguments = read_config_build_arguments() + if opt_verbose and LOG_LEVEL_OPTION in build_arguments: + i = build_arguments.index(LOG_LEVEL_OPTION) + del build_arguments[i] + del build_arguments[i] + arguments.extend(build_arguments) + if opt_unity_mode != UnityMode.DEFAULT: + unity_disabled = DISABLE_UNITY_OPTION in build_arguments + if opt_unity_mode == UnityMode.ENABLE and unity_disabled: + arguments.remove(DISABLE_UNITY_OPTION) + elif opt_unity_mode == UnityMode.DISABLE and not unity_disabled: + arguments.append(DISABLE_UNITY_OPTION) + generator = read_config(GENERATOR_KEY) + if generator != 'Ninja': + arguments.extend(['--make-spec', 'ninja']) + jobs = read_int_config(JOBS_KEY) + if jobs > 1: + arguments.extend(['-j', str(jobs)]) + if build_mode != BuildMode.BUILD: + arguments.append('--reuse-build') + if build_mode != BuildMode.RECONFIGURE: + arguments.append('--skip-cmake') + modules = read_config_modules_argument() + if modules: + arguments.append(modules) + if IS_WINDOWS and acceleration == Acceleration.INCREDIBUILD: + arg_string = ' '.join(arguments) + arguments = [INCREDIBUILD_CONSOLE, f'/command={arg_string}'] + + execute(arguments) + + elapsed_time = int(time.time() - start_time) + print(f'--- Done({elapsed_time}s) ---') + + +def build_base_docs(): + arguments = [read_config_python_binary(), "setup.py", "build_base_docs", "--log-level", + "quiet"] + for build_arg in read_config_build_arguments(): + if build_arg.startswith("--qt-src-dir="): + arguments.append(build_arg) + break + execute(arguments) + + +def run_tests(): + """Run tests redirected into a log file with a time stamp""" + logfile_name = datetime.datetime.today().strftime("test_%Y%m%d_%H%M.txt") + binary = sys.executable + command = f'"{binary}" testrunner.py test > {logfile_name}' + print(command_log_string([command], Path.cwd())) + start_time = time.time() + result = 0 if opt_dry_run else os.system(command) + elapsed_time = int(time.time() - start_time) + print(f'--- Done({elapsed_time}s) ---') + return result + + +def create_argument_parser(desc): + parser = ArgumentParser(description=desc, formatter_class=RawTextHelpFormatter) + parser.add_argument('--dry-run', '-d', action='store_true', + help='Dry run, print commands') + parser.add_argument('--edit', '-e', action='store_true', + help='Edit config file') + parser.add_argument('--reset', '-r', action='store_true', + help='Git reset hard to upstream state') + parser.add_argument('--clean', '-c', action='store_true', + help='Git clean') + parser.add_argument('--pull', '-p', action='store_true', + help='Git pull') + parser.add_argument('--build', '-b', action='store_true', + help='Build (configure + build)') + parser.add_argument('--make', '-m', action='store_true', help='Make') + parser.add_argument('--no-install', '-n', action='store_true', + help='Run --build only, do not install') + parser.add_argument('--Make', '-M', action='store_true', + help='cmake + Make (continue broken build)') + parser.add_argument('--test', '-t', action='store_true', + help='Run tests') + parser.add_argument('--Documentation', '-D', action='store_true', + help='Run build_base_docs') + parser.add_argument('--version', '-v', action='version', version='%(prog)s 1.0') + parser.add_argument('--verbose', '-V', action='store_true', + help='Turn off --quiet specified in build arguments') + parser.add_argument('--unity', '-u', action='store_true', + help='Force unity build') + parser.add_argument('--no-unity', action='store_true', + help='Turn off --unity specified in build arguments') + return parser + + +if __name__ == '__main__': + git = None + base_dir = None + config_file = None + user = None + + config_file = get_config_file('qfp_tool.conf') + argument_parser = create_argument_parser(DESC.replace('%CONFIGFILE%', str(config_file))) + options = argument_parser.parse_args() + opt_dry_run = options.dry_run + opt_verbose = options.verbose + + if options.unity: + opt_unity_mode = UnityMode.ENABLE + elif options.no_unity: + opt_unity_mode = UnityMode.DISABLE + + if options.edit: + sys.exit(edit_config_file()) + + if options.build: + build_mode = BuildMode.BUILD + elif options.make: + build_mode = BuildMode.MAKE + elif options.Make: + build_mode = BuildMode.RECONFIGURE + + if build_mode == BuildMode.NONE and not (options.clean or options.reset or options.pull + or options.Documentation or options.test): + argument_parser.print_help() + sys.exit(0) + + git = 'git' + if which(git) is None: + warnings.warn('Unable to find git', RuntimeWarning) + sys.exit(-1) + + if not config_file.exists(): + print('Create initial config file ', config_file, " ..") + with open(config_file, 'w') as f: + f.write(DEFAULT_CONFIG_FILE.format(' '.join(DEFAULT_BUILD_ARGS))) + + while not Path(".git").exists(): + cwd = Path.cwd() + cwd_s = os.fspath(cwd) + if cwd_s == '/' or (IS_WINDOWS and len(cwd_s) < 4): + warnings.warn('Unable to find git root', RuntimeWarning) + sys.exit(-1) + os.chdir(cwd.parent) + + base_dir = Path.cwd().name + + if options.clean: + run_git(['clean', '-dxf']) + + if options.reset: + run_git(['reset', '--hard', '@{upstream}']) + + if options.pull: + run_git(['pull', '--rebase']) + + if build_mode != BuildMode.NONE: + target = 'build' if options.no_install else 'install' + build(target) + + if options.Documentation: + build_base_docs() + + if options.test: + sys.exit(run_tests()) + + sys.exit(0) diff --git a/build_scripts/qp5_tool.py b/build_scripts/qp5_tool.py deleted file mode 100644 index 4ec0d639..00000000 --- a/build_scripts/qp5_tool.py +++ /dev/null @@ -1,416 +0,0 @@ -# Copyright (C) 2019 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -import datetime -import os -import re -import subprocess -import sys -import time -import warnings -from argparse import ArgumentParser, RawTextHelpFormatter -from enum import Enum -from pathlib import Path -from typing import List - -DESC = """ -Utility script for working with Qt for Python. - -Feel free to extend! - -Typical Usage: -Update and build a repository: python qp5_tool -p -b - -qp5_tool.py uses a configuration file "%CONFIGFILE%" -in the format key=value. - -It is possible to use repository-specific values by adding a key postfixed by -a dash and the repository folder base name, eg: -Modules-pyside-setup512=Core,Gui,Widgets,Network,Test - -Configuration keys: -Acceleration Incredibuild or unset -BuildArguments Arguments to setup.py -Generator Generator to be used for CMake. Currently, only Ninja is - supported. -Jobs Number of jobs to be run simultaneously -Modules Comma separated list of modules to be built - (for --module-subset=) -Python Python executable (Use python_d for debug builds on Windows) - -Arbitrary keys can be defined and referenced by $(name): - -MinimalModules=Core,Gui,Widgets,Network,Test -Modules=$(MinimalModules),Multimedia -Modules-pyside-setup-minimal=$(MinimalModules) -""" - - -class Acceleration(Enum): - NONE = 0 - INCREDIBUILD = 1 - - -class BuildMode(Enum): - NONE = 0 - BUILD = 1 - RECONFIGURE = 2 - MAKE = 3 - - -DEFAULT_BUILD_ARGS = ['--build-tests', '--skip-docs', '--quiet'] -IS_WINDOWS = sys.platform == 'win32' -INCREDIBUILD_CONSOLE = 'BuildConsole' if IS_WINDOWS else '/opt/incredibuild/bin/ib_console' -# Config file keys -ACCELERATION_KEY = 'Acceleration' -BUILDARGUMENTS_KEY = 'BuildArguments' -GENERATOR_KEY = 'Generator' -JOBS_KEY = 'Jobs' -MODULES_KEY = 'Modules' -PYTHON_KEY = 'Python' - -DEFAULT_MODULES = "Core,Gui,Widgets,Network,Test,Qml,Quick,Multimedia,MultimediaWidgets" -DEFAULT_CONFIG_FILE = f"Modules={DEFAULT_MODULES}\n" - -build_mode = BuildMode.NONE -opt_dry_run = False -opt_verbose = False - - -def which(needle: str): - """Perform a path search""" - needles = [needle] - if IS_WINDOWS: - for ext in ("exe", "bat", "cmd"): - needles.append(f"{needle}.{ext}") - - for path in os.environ.get("PATH", "").split(os.pathsep): - for n in needles: - binary = Path(path) / n - if binary.is_file(): - return binary - return None - - -def command_log_string(args: List[str], directory: Path): - result = f'[{directory.name}]' - for arg in args: - result += f' "{arg}"' if ' ' in arg else f' {arg}' - return result - - -def execute(args: List[str]): - """Execute a command and print to log""" - log_string = command_log_string(args, Path.cwd()) - print(log_string) - if opt_dry_run: - return - exit_code = subprocess.call(args) - if exit_code != 0: - raise RuntimeError(f'FAIL({exit_code}): {log_string}') - - -def run_process_output(args): - """Run a process and return its output. Also run in dry_run mode""" - std_out = subprocess.Popen(args, universal_newlines=1, - stdout=subprocess.PIPE).stdout - result = [line.rstrip() for line in std_out.readlines()] - std_out.close() - return result - - -def run_git(args): - """Run git in the current directory and its submodules""" - args.insert(0, git) # run in repo - execute(args) # run for submodules - - -def expand_reference(cache_dict, value): - """Expand references to other keys in config files $(name) by value.""" - pattern = re.compile(r"\$\([^)]+\)") - while True: - match = pattern.match(value) - if not match: - break - key = match.group(0)[2:-1] - value = value[:match.start(0)] + cache_dict[key] + value[match.end(0):] - return value - - -def editor(): - editor = os.getenv('EDITOR') - if not editor: - return 'notepad' if IS_WINDOWS else 'vi' - editor = editor.strip() - if IS_WINDOWS: - # Windows: git requires quotes in the variable - if editor.startswith('"') and editor.endswith('"'): - editor = editor[1:-1] - editor = editor.replace('/', '\\') - return editor - - -def edit_config_file(): - exit_code = -1 - try: - exit_code = subprocess.call([editor(), config_file]) - except Exception as e: - reason = str(e) - print(f'Unable to launch: {editor()}: {reason}') - return exit_code - - -""" -Config file handling, cache and read function -""" -config_dict = {} - - -def read_config_file(file_name): - """Read the config file into config_dict, expanding continuation lines""" - global config_dict - keyPattern = re.compile(r'^\s*([A-Za-z0-9\_\-]+)\s*=\s*(.*)$') - with open(file_name) as f: - while True: - line = f.readline() - if not line: - break - line = line.rstrip() - match = keyPattern.match(line) - if match: - key = match.group(1) - value = match.group(2) - while value.endswith('\\'): - value = value.rstrip('\\') - value += f.readline().rstrip() - config_dict[key] = expand_reference(config_dict, value) - - -def read_config(key): - """ - Read a value from the '$HOME/.qp5_tool' configuration file. When given - a key 'key' for the repository directory '/foo/qt-5', check for the - repo-specific value 'key-qt5' and then for the general 'key'. - """ - if not config_dict: - read_config_file(config_file) - repo_value = config_dict.get(f"{key}-{base_dir}") - return repo_value if repo_value else config_dict.get(key) - - -def read_bool_config(key): - value = read_config(key) - return value and value in ['1', 'true', 'True'] - - -def read_int_config(key, default=-1): - value = read_config(key) - return int(value) if value else default - - -def read_acceleration_config(): - value = read_config(ACCELERATION_KEY) - if value: - value = value.lower() - if value == 'incredibuild': - return Acceleration.INCREDIBUILD - return Acceleration.NONE - - -def read_config_build_arguments(): - value = read_config(BUILDARGUMENTS_KEY) - if value: - return re.split(r'\s+', value) - return DEFAULT_BUILD_ARGS - - -def read_config_modules_argument(): - value = read_config(MODULES_KEY) - if value and value != '' and value != 'all': - return f"--module-subset={value}" - return None - - -def read_config_python_binary() -> str: - binary = read_config(PYTHON_KEY) - virtual_env = os.environ.get('VIRTUAL_ENV') - if not binary: - # Use 'python3' unless virtualenv is set - use_py3 = not virtual_env and which('python3') - binary = 'python3' if use_py3 else 'python' - binary = Path(binary) - if not binary.is_absolute(): - abs_path = which(str(binary)) - if abs_path: - binary = abs_path - else: - warnings.warn(f'Unable to find "{binary}"', RuntimeWarning) - if virtual_env: - if not str(binary).startswith(virtual_env): - w = f'Python "{binary}" is not under VIRTUAL_ENV "{virtual_env}"' - warnings.warn(w, RuntimeWarning) - return str(binary) - - -def get_config_file(base_name) -> Path: - global user - home = os.getenv('HOME') - if IS_WINDOWS: - # Set a HOME variable on Windows such that scp. etc. - # feel at home (locating .ssh). - if not home: - home = os.getenv('HOMEDRIVE') + os.getenv('HOMEPATH') - os.environ['HOME'] = home - user = os.getenv('USERNAME') - config_file = Path(os.getenv('APPDATA')) / base_name - else: - user = os.getenv('USER') - config_dir = Path(home) / '.config' - if config_dir.exists(): - config_file = config_dir / base_name - else: - config_file = Path(home) / f".{base_name}" - return config_file - - -def build(target: str): - """Run configure and build steps""" - start_time = time.time() - - arguments = [] - acceleration = read_acceleration_config() - if not IS_WINDOWS and acceleration == Acceleration.INCREDIBUILD: - arguments.append(INCREDIBUILD_CONSOLE) - arguments.appendh('--avoid') # caching, v0.96.74 - arguments.extend([read_config_python_binary(), 'setup.py', target]) - build_arguments = read_config_build_arguments() - if opt_verbose and '--quiet' in build_arguments: - build_arguments.remove('--quiet') - arguments.extend(build_arguments) - generator = read_config(GENERATOR_KEY) - if generator != 'Ninja': - arguments.extend(['--make-spec', 'ninja']) - jobs = read_int_config(JOBS_KEY) - if jobs > 1: - arguments.extend(['-j', str(jobs)]) - if build_mode != BuildMode.BUILD: - arguments.extend(['--reuse-build', '--ignore-git']) - if build_mode != BuildMode.RECONFIGURE: - arguments.append('--skip-cmake') - modules = read_config_modules_argument() - if modules: - arguments.append(modules) - if IS_WINDOWS and acceleration == Acceleration.INCREDIBUILD: - arg_string = ' '.join(arguments) - arguments = [INCREDIBUILD_CONSOLE, f'/command={arg_string}'] - - execute(arguments) - - elapsed_time = int(time.time() - start_time) - print(f'--- Done({elapsed_time}s) ---') - - -def run_tests(): - """Run tests redirected into a log file with a time stamp""" - logfile_name = datetime.datetime.today().strftime("test_%Y%m%d_%H%M.txt") - binary = sys.executable - command = f'"{binary}" testrunner.py test > {logfile_name}' - print(command_log_string([command], Path.cwd())) - start_time = time.time() - result = 0 if opt_dry_run else os.system(command) - elapsed_time = int(time.time() - start_time) - print(f'--- Done({elapsed_time}s) ---') - return result - - -def create_argument_parser(desc): - parser = ArgumentParser(description=desc, formatter_class=RawTextHelpFormatter) - parser.add_argument('--dry-run', '-d', action='store_true', - help='Dry run, print commands') - parser.add_argument('--edit', '-e', action='store_true', - help='Edit config file') - parser.add_argument('--reset', '-r', action='store_true', - help='Git reset hard to upstream state') - parser.add_argument('--clean', '-c', action='store_true', - help='Git clean') - parser.add_argument('--pull', '-p', action='store_true', - help='Git pull') - parser.add_argument('--build', '-b', action='store_true', - help='Build (configure + build)') - parser.add_argument('--make', '-m', action='store_true', help='Make') - parser.add_argument('--no-install', '-n', action='store_true', - help='Run --build only, do not install') - parser.add_argument('--Make', '-M', action='store_true', - help='cmake + Make (continue broken build)') - parser.add_argument('--test', '-t', action='store_true', - help='Run tests') - parser.add_argument('--version', '-v', action='version', version='%(prog)s 1.0') - parser.add_argument('--verbose', '-V', action='store_true', - help='Turn off --quiet specified in build arguments') - return parser - - -if __name__ == '__main__': - git = None - base_dir = None - config_file = None - user = None - - config_file = get_config_file('qp5_tool.conf') - argument_parser = create_argument_parser(DESC.replace('%CONFIGFILE%', str(config_file))) - options = argument_parser.parse_args() - opt_dry_run = options.dry_run - opt_verbose = options.verbose - - if options.edit: - sys.exit(edit_config_file()) - - if options.build: - build_mode = BuildMode.BUILD - elif options.make: - build_mode = BuildMode.MAKE - elif options.Make: - build_mode = BuildMode.RECONFIGURE - - if build_mode == BuildMode.NONE and not (options.clean or options.reset - or options.pull or options.test): - argument_parser.print_help() - sys.exit(0) - - git = 'git' - if which(git) is None: - warnings.warn('Unable to find git', RuntimeWarning) - sys.exit(-1) - - if not config_file.exists(): - print('Create initial config file ', config_file, " ..") - with open(config_file, 'w') as f: - f.write(DEFAULT_CONFIG_FILE.format(' '.join(DEFAULT_BUILD_ARGS))) - - while not Path(".git").exists(): - cwd = Path.cwd() - cwd_s = os.fspath(cwd) - if cwd_s == '/' or (IS_WINDOWS and len(cwd_s) < 4): - warnings.warn('Unable to find git root', RuntimeWarning) - sys.exit(-1) - os.chdir(cwd.parent) - - base_dir = Path.cwd().name - - if options.clean: - run_git(['clean', '-dxf']) - - if options.reset: - run_git(['reset', '--hard', '@{upstream}']) - - if options.pull: - run_git(['pull', '--rebase']) - - if build_mode != BuildMode.NONE: - target = 'build' if options.no_install else 'install' - build(target) - - if options.test: - sys.exit(run_tests()) - - sys.exit(0) diff --git a/build_scripts/setup_runner.py b/build_scripts/setup_runner.py index 6a7de04c..9bcbb4af 100644 --- a/build_scripts/setup_runner.py +++ b/build_scripts/setup_runner.py @@ -93,7 +93,7 @@ class SetupRunner(object): setup_cmd.append(self.construct_cmd_line_argument(name, value)) # Add --reuse-build option if requested and not already present. - if (reuse_build and command in ('bdist_wheel', 'build', 'build_rst_docs', 'install') + if (reuse_build and command in ('bdist_wheel', 'build', 'build_base_docs', 'build_rst_docs', 'install') and not self.cmd_line_argument_is_in_args("reuse-build", modified_argv)): setup_cmd.append(self.construct_cmd_line_argument("reuse-build")) return setup_cmd diff --git a/build_scripts/utils.py b/build_scripts/utils.py index 1ee3e39a..74d9e6fc 100644 --- a/build_scripts/utils.py +++ b/build_scripts/utils.py @@ -20,7 +20,6 @@ from .log import log from . import (PYSIDE_PYTHON_TOOLS, PYSIDE_LINUX_BIN_TOOLS, PYSIDE_UNIX_LIBEXEC_TOOLS, PYSIDE_WINDOWS_BIN_TOOLS, PYSIDE_UNIX_BIN_TOOLS, PYSIDE_UNIX_BUNDLED_TOOLS) -from setuptools.errors import SetupError try: WindowsError @@ -95,8 +94,8 @@ def copyfile(src, dst, force=True, _vars=None, force_copy_symlink=False, src = Path(src.format(**_vars)) if _vars else Path(src) if isinstance(dst, str): dst = Path(dst.format(**_vars)) if _vars else Path(dst) - assert(isinstance(src, Path)) - assert(isinstance(dst, Path)) + assert (isinstance(src, Path)) + assert (isinstance(dst, Path)) if not src.exists() and not force: log.info(f"**Skipping copy file\n {src} to\n {dst}\n Source does not exist") @@ -171,8 +170,8 @@ def copydir(src, dst, _filter=None, ignore=None, force=True, recursive=True, _va src = Path(src.format(**_vars)) if _vars else Path(src) if isinstance(dst, str): dst = Path(dst.format(**_vars)) if _vars else Path(dst) - assert(isinstance(src, Path)) - assert(isinstance(dst, Path)) + assert (isinstance(src, Path)) + assert (isinstance(dst, Path)) if _vars is not None: if _filter is not None: @@ -950,7 +949,7 @@ def get_qtci_virtualEnv(python_ver, host, hostArch, targetArch): _path = Path(os.getenv(var, "")) _pExe = _path / "python.exe" if not _pExe.is_file(): - log.warn(f"Can't find python.exe from {_pExe}, using default python3") + log.warning(f"Can't find python.exe from {_pExe}, using default python3") _pExe = Path(os.getenv("PYTHON3_32_PATH")) / "python.exe" else: _pExe = Path(os.getenv("PYTHON2_32_PATH")) / "python.exe" @@ -961,7 +960,7 @@ def get_qtci_virtualEnv(python_ver, host, hostArch, targetArch): _path = Path(os.getenv(var, "")) _pExe = _path / "python.exe" if not _pExe.is_file(): - log.warn(f"Can't find python.exe from {_pExe}, using default python3") + log.warning(f"Can't find python.exe from {_pExe}, using default python3") _pExe = Path(os.getenv("PYTHON3_PATH")) / "python.exe" env_python = f"{_env}\\Scripts\\python.exe" env_pip = f"{_env}\\Scripts\\pip.exe" @@ -972,7 +971,7 @@ def get_qtci_virtualEnv(python_ver, host, hostArch, targetArch): except Exception as e: print(f"Exception {type(e).__name__}: {e}") _pExe = "python3" - return(_pExe, _env, env_pip, env_python) + return (_pExe, _env, env_pip, env_python) def run_instruction(instruction, error, initial_env=None): @@ -1135,7 +1134,7 @@ def available_pyside_tools(qt_tools_path: Path, package_for_wheels: bool = False if tool_exist(bin_path / f"{tool}.exe")]) else: lib_exec_path = qt_tools_path / "Qt" / "libexec" if package_for_wheels \ - else qt_tools_path / "libexec" + else qt_tools_path / "libexec" pyside_tools.extend([tool for tool in PYSIDE_UNIX_LIBEXEC_TOOLS if tool_exist(lib_exec_path / tool)]) if sys.platform == 'darwin': diff --git a/build_scripts/wheel_files.py b/build_scripts/wheel_files.py index ec66b957..2112bba9 100644 --- a/build_scripts/wheel_files.py +++ b/build_scripts/wheel_files.py @@ -166,6 +166,7 @@ def wheel_files_pyside_essentials() -> List[ModuleData]: module_QtQml(), module_QtQuick(), module_QtQuickControls2(), + module_QtQuickTest(), module_QtQuickWidgets(), module_QtXml(), module_QtTest(), @@ -458,6 +459,7 @@ def module_QtQml() -> ModuleData: "libQt6QmlCore", "libQt6QmlLocalStorage", "libQt6QmlModels", + "libQt6QmlNetwork", "libQt6QmlWorkerScript", "libQt6QmlXmlListModel", "libQt6QmlCompiler" @@ -559,6 +561,7 @@ def module_QtQuick() -> ModuleData: "libQt6QuickTemplates2", "libQt6QuickTest", "libQt6QuickTimeline", + "libQt6QuickTimelineBlendTrees", ] # Adding GraphicalEffects files @@ -574,12 +577,35 @@ def module_QtQuick() -> ModuleData: def module_QtQuickControls2() -> ModuleData: data = ModuleData("QuickControls2") + data.qtlib.append("libQt6QuickControls2") + data.qtlib.append("libQt6QuickControls2Basic") + data.qtlib.append("libQt6QuickControls2BasicStyleImpl") + data.qtlib.append("libQt6QuickControls2Fusion") + data.qtlib.append("libQt6QuickControls2FusionStyleImpl") + data.qtlib.append("libQt6QuickControls2Imagine") + data.qtlib.append("libQt6QuickControls2ImagineStyleImpl") data.qtlib.append("libQt6QuickControls2Impl") + data.qtlib.append("libQt6QuickControls2Material") + data.qtlib.append("libQt6QuickControls2MaterialStyleImpl") + data.qtlib.append("libQt6QuickControls2Universal") + data.qtlib.append("libQt6QuickControls2UniversalStyleImpl") + if sys.platform == "win32": + data.qtlib.append("libQt6QuickControls2WindowsStyleImpl") + elif sys.platform == "darwin": + data.qtlib.append("libQt6QuickControls2IOSStyleImpl") + data.qtlib.append("libQt6QuickControls2MacOSStyleImpl") + data.metatypes.append("qt6quickcontrols2impl_relwithdebinfo_metatypes.json") return data +def module_QtQuickTest() -> ModuleData: + data = ModuleData("QuickTest") + + return data + + def module_QtQuickWidgets() -> ModuleData: data = ModuleData("QuickWidgets") return data @@ -704,10 +730,14 @@ def module_QtQuick3D() -> ModuleData: "libQt6Quick3DEffects", "libQt6Quick3DGlslParser", "libQt6Quick3DHelpers", + "libQt6Quick3DHelpersImpl", "libQt6Quick3DIblBaker", "libQt6Quick3DParticleEffects", "libQt6Quick3DParticles", + "libQt6Quick3DPhysics", + "libQt6Quick3DPhysicsHelpers", "libQt6Quick3DRuntimeRender", + "libQt6Quick3DSpatialAudio", "libQt6Quick3DUtils", "libQt6ShaderTools", "libQt63DQuick", @@ -744,6 +774,8 @@ def module_QtQuick3D() -> ModuleData: data.qtlib.extend(_qtlib) data.metatypes.extend(_metatypes) data.extra_files.append("Qt/plugins/assetimporters/libassimp*") + data.extra_files.append("qsb*") + data.extra_files.append("balsam*") return data @@ -818,6 +850,10 @@ def module_QtMultimedia() -> ModuleData: data.translations.append("qtmultimedia_*") data.plugins = get_module_plugins(json_data) + if sys.platform == "win32": + data.extra_files.extend(["avcodec-60.dll", "avformat-60.dll", "avutil-58.dll", + "swresample-4.dll", "swscale-7.dll"]) + return data @@ -961,6 +997,8 @@ def module_QtSerialBus() -> ModuleData: def module_QtVirtualKeyboard() -> ModuleData: data = ModuleData("VirtualKeyboard") data.plugins.append("virtualkeyboard") + data.qtlib.append("libQt6VirtualKeyboardSettings") + return data diff --git a/coin/dependencies.yaml b/coin/dependencies.yaml index 2bf2feb2..c03b1b8a 100644 --- a/coin/dependencies.yaml +++ b/coin/dependencies.yaml @@ -1,6 +1,6 @@ product_dependency: ../../qt/qt5: - ref: "15b7e7434fd79334a5cf071e36cee7663fe1fb45" + ref: "3f005f1e2e88485dbf541200ba3fafcad6ea84ad" dependency_source: supermodule dependencies: [ "../../qt/qt3d", diff --git a/coin/instructions/common_environment.yaml b/coin/instructions/common_environment.yaml index 8cb3c1d5..441a0180 100644 --- a/coin/instructions/common_environment.yaml +++ b/coin/instructions/common_environment.yaml @@ -34,6 +34,18 @@ instructions: - condition: property property: host.os equals_value: Windows + - type: EnvironmentVariable + variableName: PYTHON3_PATH + variableValue: "{{ index .Env \"PYTHON3.10.0-64_PATH\"}}" + enable_if: + condition: and + conditions: + - condition: property + property: host.osVersion + equals_value: Windows_11_22H2 + - condition: property + property: host.os + equals_value: Windows - type: EnvironmentVariable variableName: TARGET_ARCHITECTURE variableValue: amd64_x86 @@ -118,7 +130,7 @@ instructions: equals_value: MacOS - type: PrependToEnvironmentVariable variableName: PATH - variableValue: "{{.Env.PYTHON3_PATH}};" + variableValue: "{{ index .Env \"PYTHON3.10.0-64_PATH\"}};" enable_if: condition: property property: host.os @@ -191,21 +203,6 @@ instructions: - condition: property property: host.os equals_value: MacOS - - type: ExecuteCommand - command: "keyring --disable" - maxTimeInSeconds: 14400 - maxTimeBetweenOutput: 1200 - enable_if: - condition: and - conditions: - - condition: property - property: host.os - equals_value: Linux - - condition: property - property: host.arch - equals_value: AARCH64 - userMessageOnFailure: > - Failed to disable keyring - type: ExecuteCommand command: "sudo apt-get install python3-pip libclang-11-dev clang -y" maxTimeInSeconds: 14400 @@ -222,7 +219,7 @@ instructions: userMessageOnFailure: > Failed to install dependencies - type: ExecuteCommand - command: "python3 -m pip install -U setuptools==67.7.2" + command: "python3 -m pip install -U setuptools==69.1.1" maxTimeInSeconds: 14400 maxTimeBetweenOutput: 1200 enable_if: diff --git a/coin/instructions/execute_build_instructions.yaml b/coin/instructions/execute_build_instructions.yaml index 4b8fb41c..317adae2 100644 --- a/coin/instructions/execute_build_instructions.yaml +++ b/coin/instructions/execute_build_instructions.yaml @@ -1,7 +1,7 @@ type: Group instructions: - type: ExecuteCommand - command: "python3 -m pip install -U setuptools==67.7.2" + command: "python3 -m pip install -U setuptools==69.1.1" maxTimeInSeconds: 14400 maxTimeBetweenOutput: 1200 enable_if: @@ -40,22 +40,15 @@ instructions: equals_value: MacOS userMessageOnFailure: > Failed to execute build instructions on macOS - - type: EnvironmentVariable - variableName: PYSIDE_SIGNING_DIR - variableValue: "{{.AgentWorkingDir}}\\pyside\\{{.Env.TESTED_MODULE_COIN}}\\build\\qfpa-p3.8\\package_for_wheels" - enable_if: - condition: property - property: host.osVersion - contains_value: "Windows_11" - type: EnvironmentVariable variableName: PYSIDE_SIGNING_DIR variableValue: "{{.AgentWorkingDir}}\\pyside\\{{.Env.TESTED_MODULE_COIN}}\\build\\qfpa-p3.10\\package_for_wheels" enable_if: condition: property - property: host.osVersion - contains_value: "Windows_10" + property: host.os + equals_value: Windows - type: ExecuteCommand - command: "{{.Env.interpreter}} -m pip install -U pip setuptools==67.7.2 --user" + command: "{{.Env.interpreter}} -m pip install -U pip setuptools==69.1.1 --user" maxTimeInSeconds: 14400 maxTimeBetweenOutput: 1200 enable_if: @@ -75,7 +68,7 @@ instructions: userMessageOnFailure: > Failed to execute build instructions on Linux - type: ExecuteCommand - command: "c:\\users\\qt\\MSVC.bat {{.Env.PYTHON3_PATH}}\\python.exe -m pip install -U setuptools==67.7.2" + command: "c:\\users\\qt\\MSVC.bat {{.Env.PYTHON3_PATH}}\\python.exe -m pip install -U setuptools==69.1.1" maxTimeInSeconds: 14400 maxTimeBetweenOutput: 1200 enable_if: diff --git a/coin/instructions/execute_test_instructions.yaml b/coin/instructions/execute_test_instructions.yaml index 23e975ce..780b27ec 100644 --- a/coin/instructions/execute_test_instructions.yaml +++ b/coin/instructions/execute_test_instructions.yaml @@ -5,7 +5,7 @@ enable_if: not_contains_value: LicenseCheck instructions: - type: ExecuteCommand - command: "python3 -m pip install -U setuptools==67.7.2" + command: "python3 -m pip install -U setuptools==69.1.1" maxTimeInSeconds: 14400 maxTimeBetweenOutput: 1200 enable_if: @@ -45,7 +45,7 @@ instructions: userMessageOnFailure: > Failed to execute test instructions on macOS - type: ExecuteCommand - command: "{{.Env.interpreter}} -m pip install -U pip setuptools==67.7.2 --user" + command: "{{.Env.interpreter}} -m pip install -U pip setuptools==69.1.1 --user" maxTimeInSeconds: 14400 maxTimeBetweenOutput: 1200 enable_if: @@ -65,7 +65,7 @@ instructions: userMessageOnFailure: > Failed to execute test instructions on Linux - type: ExecuteCommand - command: "c:\\users\\qt\\MSVC.bat {{.Env.PYTHON3_PATH}}\\python.exe -m pip install -U pip setuptools==67.7.2 --user" + command: "c:\\users\\qt\\MSVC.bat {{.Env.PYTHON3_PATH}}\\python.exe -m pip install -U pip setuptools==69.1.1 --user" maxTimeInSeconds: 14400 maxTimeBetweenOutput: 1200 enable_if: diff --git a/coin/instructions/find_path_to_msvc_compiler.yaml b/coin/instructions/find_path_to_msvc_compiler.yaml index 8f9bed82..015a86ea 100644 --- a/coin/instructions/find_path_to_msvc_compiler.yaml +++ b/coin/instructions/find_path_to_msvc_compiler.yaml @@ -18,6 +18,13 @@ instructions: condition: property property: host.compiler equals_value: MSVC2019 + - type: EnvironmentVariable + variableName: VC_SCRIPT + variableValue: "\\Program Files\\Microsoft Visual Studio\\2022\\Professional\\VC\\Auxiliary\\Build\\vcvarsall.bat" + enable_if: + condition: property + property: host.compiler + equals_value: MSVC2022 - type: WriteFile fileContents: "call \"{{.Env.VC_SCRIPT}}\" {{.Env.TARGET_ARCHITECTURE}} \r\ncmd /c %*" filename: "c:\\users\\qt\\MSVC.bat" diff --git a/coin/module_config.yaml b/coin/module_config.yaml index 03f865bd..e5c2fdc8 100644 --- a/coin/module_config.yaml +++ b/coin/module_config.yaml @@ -18,50 +18,69 @@ accept_configuration: not_contains_value: UseLegacyInstructions - condition: property # MinGW and msvc2015 are not supported property: target.compiler - not_in_values: [Mingw, MSVC2015] + not_in_values: [Mingw, MSVC2015,Clang] - condition: property # Webassembly property: target.osVersion not_equals_value: WebAssembly - condition: property # Windows on Arm property: target.arch not_equals_value: ARM64 + - condition: property # Windows on Arm host build + property: target.arch + not_equals_value: AARCH64 - condition: property property: features not_contains_value: DebianPackaging + - condition: property + property: target.osVersion + not_equals_value: VxWorks - condition: and conditions: - - condition: property # No cross compilation - property: host.osVersion - equals_value: MacOS_10_15 + - condition: property + property: host.os + equals_value: MacOS - condition: property property: features contains_value: TestOnly - condition: property property: features contains_value: Packaging + - condition: property + property: target.os + not_contains_value: Android + - condition: property + property: target.os + not_contains_value: IOS - condition: and conditions: - condition: property property: host.osVersion equals_value: MacOS_11_00 + - condition: property + property: host.arch + equals_value: ARM64 + - condition: property + property: features + contains_value: TestOnly - condition: property property: features contains_value: Packaging - - condition: and + - condition: and # Restore LoA config conditions: - condition: property property: host.osVersion - equals_value: MacOS_11_00 + equals_value: Debian_11_6 - condition: property property: host.arch - equals_value: ARM64 + equals_value: AARCH64 - condition: property property: features - contains_value: TestOnly + not_contains_value: DebianPackaging - condition: property property: features contains_value: Packaging + machine_type: Build: cores: 8 diff --git a/coin_build_instructions.py b/coin_build_instructions.py index 0f278dbc..83d46977 100644 --- a/coin_build_instructions.py +++ b/coin_build_instructions.py @@ -157,10 +157,7 @@ if __name__ == "__main__": python_ver = "3.11" wheel_package_dir = "qfpa-p3.6" if CI_TARGET_OS in ["Windows"]: - if (os.environ.get("HOST_OSVERSION_COIN")).startswith("windows_10"): - python_ver = "3.10.0" - else: - python_ver = "3.8.1" + python_ver = "3.10.0" if CI_TEST_PHASE in ["ALL", "BUILD"]: call_setup(python_ver, "BUILD") # Until CI has a feature to set more dynamic signing dir, make sure it actually exist diff --git a/coin_test_instructions.py b/coin_test_instructions.py index 219fcf37..b0e71fba 100644 --- a/coin_test_instructions.py +++ b/coin_test_instructions.py @@ -96,10 +96,7 @@ def run_test_instructions(): # In win machines, there are additional python versions to test with if CI_HOST_OS == "Windows": - if (os.environ.get('HOST_OSVERSION_COIN')).startswith('windows_10'): - call_testrunner("3.10.0", str(testRun)) - else: - call_testrunner("3.8.1", str(testRun)) + call_testrunner("3.10.0", str(testRun)) elif CI_HOST_OS == "Linux": call_testrunner("3.11", str(testRun)) else: diff --git a/create_wheels.py b/create_wheels.py index 1b34fa40..063b59c4 100644 --- a/create_wheels.py +++ b/create_wheels.py @@ -5,6 +5,7 @@ import os import platform import sys import importlib +import json from argparse import ArgumentParser, Namespace from dataclasses import dataclass from pathlib import Path @@ -45,7 +46,21 @@ def get_version_from_package(name: str, package_path: Path) -> str: return version, f"{name}.__init__.__version__" -def get_manifest(wheel_name: str, data: List[ModuleData]) -> str: +def create_module_plugin_json(wheel_name: str, data: List[ModuleData], package_path: Path): + all_plugins = {} + + for module in data: + all_plugins[module.name] = getattr(module, "plugins") + + # write the dictionary modules->plugins dictionary to a .json file and include this .json file + # This file is picked up by the deployment tool to figure out the plugin dependencies + # of a PySide6 application + if all_plugins: + with open(f"{package_path}/PySide6/{wheel_name}.json", 'w') as fp: + json.dump(all_plugins, fp, indent=4) + + +def get_manifest(wheel_name: str, data: List[ModuleData], package_path: Path) -> str: lines = [] for module in data: @@ -74,6 +89,9 @@ def get_manifest(wheel_name: str, data: List[ModuleData]) -> str: lines.append("recursive-exclude PySide6/Qt/qml *.debug") lines.append("prune PySide6/Qt/qml/QtQuick3D/MaterialEditor") + # adding PySide6_Essentials.json and PySide6_Addons.json + lines.append(f"include PySide6/{wheel_name}.json") + return "\n".join(lines) @@ -441,7 +459,13 @@ if __name__ == "__main__": with open(pyproject_toml_path, "w") as f: f.write(pyproject_toml_content) - # 3. Create the 'MANIFEST.in' + # 3. Create PySide_Essentials.json and PySide_Addons.json + # creates a json file mapping each Qt module to the possible plugin dependencies + if data is not None: + print(f"-- Creating {name}.json") + create_module_plugin_json(name, data, package_path) + + # 4. Create the 'MANIFEST.in' # Special case for shiboken and shiboken_generator # so we copy the whole directory, only PySide and derivatives # will need to have specific information @@ -449,7 +473,7 @@ if __name__ == "__main__": if data is None: manifest_content = get_simple_manifest(name) else: - manifest_content = get_manifest(name, data) + manifest_content = get_manifest(name, data, package_path) with open(package_path / "MANIFEST.in", "w") as f: f.write(manifest_content) @@ -461,7 +485,7 @@ if __name__ == "__main__": for fname in _files: copy(fname, package_path) - # 5. call the build module to create the wheel + # 6. call the build module to create the wheel print("-- Creating wheels") if not verbose: _runner = pyproject_hooks.quiet_subprocess_runner @@ -470,7 +494,7 @@ if __name__ == "__main__": builder = build.ProjectBuilder(package_path, runner=_runner) builder.build("wheel", "dist") - # 6. Copy wheels back + # 7. Copy wheels back print("-- Copying wheels to dist/") dist_path = Path("dist") if not dist_path.is_dir(): diff --git a/doc/changelogs/changes-6.6.3 b/doc/changelogs/changes-6.6.3 new file mode 100644 index 00000000..88756492 --- /dev/null +++ b/doc/changelogs/changes-6.6.3 @@ -0,0 +1,60 @@ +Qt for Python 6.6.3 is a bug-fix release. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +https://doc.qt.io/qtforpython/ + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* PySide6 * +**************************************************************************** + + - pyside6-project now also builds translation (.qm) files. + - [PYSIDE-769] QtAsyncio: Fix a missing attribute error on Windows. Fix + - [PYSIDE-2641] QtAsyncio.run() not returning the result of a coroutine. It + is now possible to disable the signal handler for SIGINT by + passing a parameter to run(). The loop policy is reset after + run() finishes. + - [PYSIDE-1112] Documentation: The list of tools has been updated. + - [PYSIDE-1112] Documentation: A page on the pyside6-project tool + has been added. + - [PYSIDE-1955] Type hints: Add support for classmethods + - [PYSIDE-2206] An exit crash of the audiooutput example has been fixed. + - [PYSIDE-2263] Type hints: Support for class attributes has been added. + - [PYSIDE-2468] Documentation on the PySide6 Permission API has been added. + - [PYSIDE-2590] Some fixes have been made be able to cope with namespaced Qt + builds. + - [PYSIDE-2599] The conversion of QVariant<->QMatrixx has been fixed. + - [PYSIDE-2603] Documentation: A broken link to the widget styling tutorial + has been fixed. + - [PYSIDE-2610] Inheritance from QAbstractTextureImage/ + QTextureImageDataGenerator has been fixed. + - [PYSIDE-2612] Plain CMake builds with Limited API on Windows have been + fixed. + - [PYSIDE-2613] Connecting to signals with QFlags<> arguments has been fixed. + - [PYSIDE-2627] A regression breaking calling Signal.connect() with + QObject-derived callables has been fixed. + - [PYSIDE-2628] A crash using struct.unpack() on a QByteArray with + Limited API has been fixed. + - [PYSIDE-2638] A crash when adding None to a QLayout has been fixed. + - [PYSIDE-2639] macOS: A segmentation fault in QLocale.system() has been + fixed. + - [PYSIDE-2640] pyside6-qml now uses a QQuickView when the rootobject is a + QQuickItem. + +**************************************************************************** +* Shiboken6 * +**************************************************************************** + + - [PYSIDE-2505] Command line option parsing has been improved to skip empty + tokens when in include paths. + - [PYSIDE-2619] An ODR violation in shibobken code has been fixed, enabling + LTO. diff --git a/doc/changelogs/changes-6.7.0 b/doc/changelogs/changes-6.7.0 new file mode 100644 index 00000000..cf515716 --- /dev/null +++ b/doc/changelogs/changes-6.7.0 @@ -0,0 +1,127 @@ +Qt for Python 6.7.0 is a minor release. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +https://doc.qt.io/qtforpython/ + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* PySide6 * +**************************************************************************** + + - setup.py now uses CMake Unity Build Mode by default. + - The namespace QAudio has been renamed to QtAudio in Qt 6.7. An alias + has been created, but it may not work in all cases. + - pyside6-project now has an lupdate mode updating translation files (.ts) + from the sources. + - Python code has been updated to be free of flake8 warnings using + a custom configuration. + - For deployment, the supported version of Nuitka has been raised to 2.1.0. + - singleShot timers with a timeout of 0 now call the C++ function (which + directly calls QMetaObject::invokeMethod) instead of manually creating and + starting a timer object, slightly improving performance. + - [PYSIDE-769] QtAsyncio: Fix a missing attribute error on Windows. Fix + - [PYSIDE-2641] QtAsyncio.run() not returning the result of a coroutine. It + is now possible to disable the signal handler for SIGINT by + passing a parameter to run(). The loop policy is reset after + run() finishes. + - [PYSIDE-838] Functions qCompress/qUncompress() taking a PyBuffer/len + arguments have been fixed. + - [PYSIDE-1106] Documentation: Decorators, global functions and enumerations + are now documented add indexed from the module page. The class + documentation now uses more sphinx domain directives. + Pages for all tools have been added. The order of the examples + list has been changed to show relevant examples first. + - [PYSIDE-1275] QObject.disconnect() now returns False with a warning instead + of raising an exception for non-fatal cases. + - [PYSIDE-1612] Deployment: Issues when using pyenv have been fixed. The + finding of dependent modules has been improved. On Windows, + this now requires the dumpbin tool, which is added to the path + by calling the vcvarsall.bat script of Microsoft Visual + Studio. + - [PYSIDE-1612] Android Deployment: The NDK version in the tool has been + updated. Python has been updated to 3.11. CMake version 3.23 + is now required. The wheels are no longer built with + --limited-api. The generated pysidedeploy.spec has an explicit + group called `android` which stores the wheels and plugins. + - [PYSIDE-1906] Hash value calculation has been optimized. + - [PYSIDE-2206] The colorpaletteclient demo as well as the hellographs and + simplerhiwidget examples have been added. + - [PYSIDE-2215] Intersphinx support has been improved. + - [PYSIDE-2280] A type for os.PathLike type hints has been added. + - [PYSIDE-2404] Application startup time has been significantly decreased + by creating the types on demand. The importing of numpy + has also been deferred since it caused quite some delay. + - [PYSIDE-2432] A static create() function for decorated QML singletons + has been added. + - [PYSIDE-2484] The QML type registration code has been ported to use + RegisterTypeAndRevisions. + - [PYSIDE-2535] Obtaining DBUS properties from QDBusInterface has been fixed. + As a consequence though, it is no longer possible to derive a + Python class with signals/slots from QDBusInterface. + - [PYSIDE-2504] Documentation: Option 'build_rst_docs' has been deprecated in + favor of 'build_base_docs'. + - [PYSIDE-2524] It is now possible to connect signals to slots/lambdas with + more arguments provided they have default parameters. + - [PYSIDE-2524] The signal connection code has been optimized. + - [PYSIDE-2543] QtQuickTest has been added. + - [PYSIDE-2576] Documentation: The extendedexplorer tutorial has been improved. + - [PYSIDE-2590] Some fixes have been made be able to cope with namespaced Qt + builds. + - [PYSIDE-2610] The QTextureImageData methods returning an enumeration of + QOpenGLTexture have been added. + - [PYSIDE-2605] An error about the C++ object being deleted when calling + QWidget.style() in an embedded application has been fixed. + - [PYSIDE-2468] Deployment: Support for the Qt permission API has been added. + macOS bundle applications are now created. + - [PYSIDE-2597] Desktop Deployment: The the plugins included have been + optimized. + - [PYSIDE-2633] C++ typedefs of container instantiations are now + registered in Python under their name, making possible + to decorate slots with those names (for example, + "QRemoteObjectSourceLocation"). + - [PYSIDE-2639] Segmentation fault with QLocale.system() has been fixed. + - [PYSIDE-2652] A bug when passing values of unsigned long long type + exceeding the long long maximum value to float + parameters has been fixed. + - [PYSIDE-2663] A crash in QtWebEngine browsing https://outlook.com has been + fixed. + - [PYSIDE-2668] A bug when comparing QOperatingSystemVersion::OSType + has been fixed. + - [QTBUG-119785] The Filesystemexplorer-example has been updated. + +**************************************************************************** +* Shiboken6 * +**************************************************************************** + + - [PYSIDE-31] Shiboken.wrapInstance() now returns existing instances + (preserving ids). + - [PYSIDE-560] libshiboken/Limited API: No longer needed PyTypeObject slots + have been disabled in the PyTypeObject helper struct. + - [PYSIDE-1106] A typesystem attribute providing a hint to a documentation + file has been added to function/enum type entries + (for globals). + - [PYSIDE-1106] Documentation can now be injected from separate .rst files. + - [PYSIDE-1106] Documentation injected into classes with "append" will now + be appended to the class description instead of being written + at the end of the page. + - [PYSIDE-1106] Documentation: It is now possible to inject documentation + for parameters of added functions. + - [PYSIDE-2230] Python 3.12: Hidden Type Extensions according to PEP 697 + are now used instead of shadow dictionaries. + - [PYSIDE-2404] The generated type index constants are no longer in + uppercase. Uppercase is retained until deprecation in + PySide7. + - [PYSIDE-2447] A typesystem attribute to generate submodules has been added. + - [PYSIDE-2535] Generating the Qt meta object functions handling the PySide6 + signals can now be disabled by a typesystem attribute. This + is useful for classes using dynamic meta objects, for + example QDBusInterface. diff --git a/doc/changelogs/changes-6.7.1 b/doc/changelogs/changes-6.7.1 new file mode 100644 index 00000000..66263ed8 --- /dev/null +++ b/doc/changelogs/changes-6.7.1 @@ -0,0 +1,61 @@ +Qt for Python 6.7.1 is a bug-fix release. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +https://doc.qt.io/qtforpython/ + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* PySide6 * +**************************************************************************** + + - [PYSIDE-487] A number of missing classes have been added. + - [PYSIDE-2629] Tooling: pyside6-qsb, pyside6-balsam and pyside6-balsamui + have been added. + - [PYSIDE-2644] QtAsyncio: An issue with tasks with loop not cancelling + has been fixed. + - [PYSIDE-2663] A crash when browsing https://outlook.com has been fixed. + - [PYSIDE-2665] A syntax error in the pyi-files has been fixed. + - [PYSIDE-2668] The comparison of QOperatingSystemVersion.OSType has been + fixed. + - [PYSIDE-2675] Lazy Load: An issue with polymorphic classes has been + fixed. + - [PYSIDE-2676] A crash with Python 3.12 when creating classes from + meta classes has been fixed. + - [PYSIDE-2685] An error in the pyi-files related to the import of + NoneType has been fixed. + - [PYSIDE-2686] Missing imports for types of return values + have been added to the pyi-files. + - [PYSIDE-2698] A crash when querying the size of QtQml.ListProperty + has been fixed and documentation for QtQml.ListProperty + has been added. + - [PYSIDE-2705] Warnings about failures of QObject.disconnect() can + now be suppressed. + - [PYSIDE-2709] A bug using legacy qmlRegisterType() for class hierarchies + has been fixed. + - [QTBUG-123997] Multimedia: The renaming of the namespace QAudio to + QtAudio has been undone following a revert in Qt. + +**************************************************************************** +* Shiboken6 * +**************************************************************************** + + - [PYSIDE-2590] An attribute for global inline namespace scopes has been + added. + - [PYSIDE-2602] Generate Python override code for added virtuals + - [PYSIDE-2602] Support for virtual functions with return type + modifications has been added and the function + QWebEnginePage.javaScriptPrompt() + has been fixed accordingly. + - [PYSIDE-2675] A code snippet placeholder for the base class for + polymorphic-id-expressions has been added, fixing + a potentially undefined behavior when using the + derived classes. diff --git a/doc/changelogs/changes-6.7.2 b/doc/changelogs/changes-6.7.2 new file mode 100644 index 00000000..44015f2c --- /dev/null +++ b/doc/changelogs/changes-6.7.2 @@ -0,0 +1,50 @@ +Qt for Python 6.7.2 is a bug-fix release. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +https://doc.qt.io/qtforpython/ + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* PySide6 * +**************************************************************************** + + - [PYSIDE-1612] Android Deployment now used the development branch of p4a. + - [PYSIDE-2712] Type hints: Modified types are no longer considered + as eligible implicit conversions. + - [PYSIDE-2745] Exceptions occurring in a slot connected to a + 0-delay singleShot timer signal are no longer suppressed. + - [PYSIDE-2747] Running on Fedora 40 with Python 3.12.3-2 has been fixed. + - [PYSIDE-2748] Type hints: QTranslator.translate() has been fixed. + - [PYSIDE-2749] Lazy Load: A performance regression showing in Qt event + filters has been fixed. + - [PYSIDE-2750] Qt6VirtualKeyboardSettings have been added to the wheel. + - [PYSIDE-2756] Type hints: The return type of QItemSelection.__init__() + has been fixed. + - [PYSIDE-2758] QQuickWebEngineProfile.setUrlRequestInterceptor() + has been added. + - [PYSIDE-2759] The ownership of cache object passed to + QNetworkAccessManager.setCache() has been fixed. + - [PYSIDE-2762] Type hints: The return type of + QModelIndex.internalPointer() has been fixed. + - [PYSIDE-2767] Type hints: An error checking the property decorator + has been fixed. + - [PYSIDE-2768] Type hints: smart pointer signatures have been fixed. + +**************************************************************************** +* Shiboken6 * +**************************************************************************** + + - [PYSIDE-2764] The missing declaration of the deprecated variable + cppApiVariableNameOld has been added to the generated + headers. + - [PYSIDE-2769] Finding the clang include directories on + manylinux_2_28_x86_64 has been fixed. diff --git a/examples/async/eratosthenes/eratosthenes_asyncio.py b/examples/async/eratosthenes/eratosthenes_asyncio.py index 598d9b4b..c5d7fc2f 100644 --- a/examples/async/eratosthenes/eratosthenes_asyncio.py +++ b/examples/async/eratosthenes/eratosthenes_asyncio.py @@ -131,4 +131,4 @@ if __name__ == "__main__": main_window.show() - QtAsyncio.run(eratosthenes.start()) + QtAsyncio.run(eratosthenes.start(), handle_sigint=True) diff --git a/examples/async/minimal/minimal_asyncio.py b/examples/async/minimal/minimal_asyncio.py index 4545f35d..a6c4708b 100644 --- a/examples/async/minimal/minimal_asyncio.py +++ b/examples/async/minimal/minimal_asyncio.py @@ -1,7 +1,7 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause -from PySide6.QtCore import (Qt, QObject, Signal, Slot) +from PySide6.QtCore import Qt from PySide6.QtWidgets import (QApplication, QLabel, QMainWindow, QPushButton, QVBoxLayout, QWidget) import PySide6.QtAsyncio as QtAsyncio @@ -12,8 +12,6 @@ import sys class MainWindow(QMainWindow): - start_signal = Signal() - def __init__(self): super().__init__() @@ -26,37 +24,17 @@ class MainWindow(QMainWindow): layout.addWidget(self.text, alignment=Qt.AlignmentFlag.AlignCenter) async_trigger = QPushButton(text="What is the question?") - async_trigger.clicked.connect(self.async_start) + async_trigger.clicked.connect(lambda: asyncio.ensure_future(self.set_text())) layout.addWidget(async_trigger, alignment=Qt.AlignmentFlag.AlignCenter) - @Slot() - def async_start(self): - self.start_signal.emit() - async def set_text(self): await asyncio.sleep(1) self.text.setText("What do you get if you multiply six by nine?") -class AsyncHelper(QObject): - - def __init__(self, worker, entry): - super().__init__() - self.entry = entry - self.worker = worker - if hasattr(self.worker, "start_signal") and isinstance(self.worker.start_signal, Signal): - self.worker.start_signal.connect(self.on_worker_started) - - @Slot() - def on_worker_started(self): - asyncio.ensure_future(self.entry()) - - if __name__ == "__main__": app = QApplication(sys.argv) main_window = MainWindow() - async_helper = AsyncHelper(main_window, main_window.set_text) - main_window.show() - QtAsyncio.run() + QtAsyncio.run(handle_sigint=True) diff --git a/examples/bluetooth/btscanner/ui_device.py b/examples/bluetooth/btscanner/ui_device.py index 0740afa7..b443b2bc 100644 --- a/examples/bluetooth/btscanner/ui_device.py +++ b/examples/bluetooth/btscanner/ui_device.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'device.ui' ## -## Created by: Qt User Interface Compiler version 6.2.3 +## Created by: Qt User Interface Compiler version 6.7.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ diff --git a/examples/bluetooth/btscanner/ui_service.py b/examples/bluetooth/btscanner/ui_service.py index fc1f45fe..ccc36677 100644 --- a/examples/bluetooth/btscanner/ui_service.py +++ b/examples/bluetooth/btscanner/ui_service.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'service.ui' ## -## Created by: Qt User Interface Compiler version 6.2.3 +## Created by: Qt User Interface Compiler version 6.7.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ diff --git a/examples/bluetooth/heartrate_game/connectionhandler.py b/examples/bluetooth/heartrate_game/connectionhandler.py index 421446c8..7bf60bbc 100644 --- a/examples/bluetooth/heartrate_game/connectionhandler.py +++ b/examples/bluetooth/heartrate_game/connectionhandler.py @@ -7,9 +7,9 @@ from PySide6.QtBluetooth import QBluetoothLocalDevice from PySide6.QtQml import QmlElement from PySide6.QtCore import QObject, Property, Signal, Slot, Qt -from heartrate_global import simulator, is_android +from heartrate_global import simulator, is_android, error_not_nuitka -if is_android: +if is_android or sys.platform == "darwin": from PySide6.QtCore import QBluetoothPermission # To be used on the @QmlElement decorator @@ -58,7 +58,8 @@ class ConnectionHandler(QObject): self.deviceChanged.emit() def initLocalDevice(self): - if is_android: + if is_android or sys.platform == "darwin": + error_not_nuitka() permission = QBluetoothPermission() permission.setCommunicationModes(QBluetoothPermission.Access) permission_status = qApp.checkPermission(permission) # noqa: F821 diff --git a/examples/bluetooth/heartrate_game/devicefinder.py b/examples/bluetooth/heartrate_game/devicefinder.py index 5cabd1b5..e581d12e 100644 --- a/examples/bluetooth/heartrate_game/devicefinder.py +++ b/examples/bluetooth/heartrate_game/devicefinder.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +import sys from PySide6.QtBluetooth import (QBluetoothDeviceDiscoveryAgent, QBluetoothDeviceInfo) @@ -8,9 +9,9 @@ from PySide6.QtCore import QTimer, Property, Signal, Slot, Qt from bluetoothbaseclass import BluetoothBaseClass from deviceinfo import DeviceInfo -from heartrate_global import simulator, is_android +from heartrate_global import simulator, is_android, error_not_nuitka -if is_android: +if is_android or sys.platform == "darwin": from PySide6.QtCore import QBluetoothPermission # To be used on the @QmlElement decorator @@ -46,7 +47,8 @@ class DeviceFinder(BluetoothBaseClass): @Slot() def startSearch(self): - if is_android: + if is_android or sys.platform == "darwin": + error_not_nuitka() permission = QBluetoothPermission() permission.setCommunicationModes(QBluetoothPermission.Access) permission_status = qApp.checkPermission(permission) # noqa: F821 diff --git a/examples/bluetooth/heartrate_game/heartrate_global.py b/examples/bluetooth/heartrate_game/heartrate_global.py index 584c44d2..de5c37ac 100644 --- a/examples/bluetooth/heartrate_game/heartrate_global.py +++ b/examples/bluetooth/heartrate_game/heartrate_global.py @@ -1,7 +1,7 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause import os - +import sys _simulator = False @@ -17,3 +17,14 @@ def set_simulator(s): is_android = os.environ.get('ANDROID_ARGUMENT') + + +def error_not_nuitka(): + """Errors and exits for macOS if run in interpreted mode. + """ + is_nuitka = "__compiled__" in globals() + if not is_nuitka and sys.platform == "darwin": + print("This example does not work on macOS when Python is run in interpreted mode." + "For this example to work on macOS, package the example using pyside6-deploy" + "For more information, read `Notes for Developer` in the documentation") + sys.exit(0) diff --git a/examples/charts/chartthemes/ui_themewidget.py b/examples/charts/chartthemes/ui_themewidget.py index 231813a6..711615b3 100644 --- a/examples/charts/chartthemes/ui_themewidget.py +++ b/examples/charts/chartthemes/ui_themewidget.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'themewidget.ui' ## -## Created by: Qt User Interface Compiler version 6.2.3 +## Created by: Qt User Interface Compiler version 6.7.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -64,7 +64,7 @@ class Ui_ThemeWidgetForm(object): self.horizontalLayout.addWidget(self.antialiasCheckBox) - self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) + self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum) self.horizontalLayout.addItem(self.horizontalSpacer) diff --git a/examples/corelib/ipc/sharedmemory/ui_dialog.py b/examples/corelib/ipc/sharedmemory/ui_dialog.py index 035b60d6..2cd544f4 100644 --- a/examples/corelib/ipc/sharedmemory/ui_dialog.py +++ b/examples/corelib/ipc/sharedmemory/ui_dialog.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'dialog.ui' ## -## Created by: Qt User Interface Compiler version 6.2.3 +## Created by: Qt User Interface Compiler version 6.7.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ diff --git a/examples/demos/colorpaletteclient/ColorPalette/ColorDialogDelete.qml b/examples/demos/colorpaletteclient/ColorPalette/ColorDialogDelete.qml new file mode 100644 index 00000000..0fd26e4d --- /dev/null +++ b/examples/demos/colorpaletteclient/ColorPalette/ColorDialogDelete.qml @@ -0,0 +1,71 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import QtExampleStyle + +Popup { + id: colorDeleter + padding: 10 + modal: true + focus: true + anchors.centerIn: parent + closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent + signal deleteClicked(int cid) + + property int colorId: -1 + + property string colorName: "" + + function maybeDelete(color_id, name) { + colorName = name + colorId = color_id + open() + } + + + ColumnLayout { + anchors.fill: parent + spacing: 10 + + Text { + color: "#222222" + text: qsTr("Delete Color?") + font.pixelSize: 16 + font.bold: true + } + + Text { + color: "#222222" + text: qsTr("Are you sure, you want to delete color") + " \"" + colorDeleter.colorName + "\"?" + font.pixelSize: 12 + } + + RowLayout { + Layout.fillWidth: true + spacing: 10 + + Button { + Layout.fillWidth: true + text: qsTr("Cancel") + onClicked: colorDeleter.close() + } + + Button { + Layout.fillWidth: true + text: qsTr("Delete") + + buttonColor: "#CC1414" + textColor: "#FFFFFF" + + onClicked: { + colorDeleter.deleteClicked(colorDeleter.colorId) + colorDeleter.close() + } + } + } + } +} diff --git a/examples/demos/colorpaletteclient/ColorPalette/ColorDialogEditor.qml b/examples/demos/colorpaletteclient/ColorPalette/ColorDialogEditor.qml new file mode 100644 index 00000000..cba6e5a7 --- /dev/null +++ b/examples/demos/colorpaletteclient/ColorPalette/ColorDialogEditor.qml @@ -0,0 +1,139 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Dialogs + +import QtExampleStyle + +Popup { + id: colorEditor + // Popup for adding or updating a color + padding: 10 + modal: true + focus: true + anchors.centerIn: parent + closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent + signal colorAdded(string name, string color, string pantone_value) + signal colorUpdated(string name, string color, string pantone_value, int cid) + + property bool newColor: true + property int colorId: -1 + property alias currentColor: colordialogButton.buttonColor + + function createNewColor() { + newColor = true + colorNameField.text = "cute green" + colorRGBField.text = "#41cd52" + colorPantoneField.text = "PMS 802C" + open() + } + + function updateColor(color_id, name, color, pantone_value) { + newColor = false + colorNameField.text = name + currentColor = color + colorPantoneField.text = pantone_value + colorId = color_id + open() + } + + ColorDialog { + id: colorDialog + title: qsTr("Choose a color") + onAccepted: { + colorEditor.currentColor = Qt.color(colorDialog.selectedColor) + colorDialog.close() + } + onRejected: { + colorDialog.close() + } + } + + ColumnLayout { + anchors.fill: parent + spacing: 10 + + GridLayout { + columns: 2 + rowSpacing: 10 + columnSpacing: 10 + + Label { + text: qsTr("Color Name") + } + TextField { + id: colorNameField + padding: 10 + } + + Label { + text: qsTr("Pantone Value") + } + TextField { + id: colorPantoneField + padding: 10 + } + + Label { + text: qsTr("Rgb Value") + } + + TextField { + id: colorRGBField + text: colorEditor.currentColor.toString() + readOnly: true + padding: 10 + } + } + + Button { + id: colordialogButton + Layout.fillWidth: true + Layout.preferredHeight: 30 + text: qsTr("Set Color") + textColor: isColorDark(buttonColor) ? "#E6E6E6" : "#191919" + + onClicked: colorDialog.open() + + function isColorDark(color) { + return (0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b) < 0.5; + } + } + + RowLayout { + Layout.fillWidth: true + spacing: 10 + + Button { + text: qsTr("Cancel") + onClicked: colorEditor.close() + Layout.fillWidth: true + } + + Button { + Layout.fillWidth: true + text: colorEditor.newColor ? qsTr("Add") : qsTr("Update") + + buttonColor: "#2CDE85" + textColor: "#FFFFFF" + + onClicked: { + if (colorEditor.newColor) { + colorEditor.colorAdded(colorNameField.text, + colorRGBField.text, + colorPantoneField.text) + } else { + colorEditor.colorUpdated(colorNameField.text, + colorRGBField.text, + colorPantoneField.text, + colorEditor.colorId) + } + colorEditor.close() + } + } + } + } +} diff --git a/examples/demos/colorpaletteclient/ColorPalette/ColorView.qml b/examples/demos/colorpaletteclient/ColorPalette/ColorView.qml new file mode 100644 index 00000000..c6ad36f8 --- /dev/null +++ b/examples/demos/colorpaletteclient/ColorPalette/ColorView.qml @@ -0,0 +1,381 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Effects +import QtQuick.Shapes + +import QtExampleStyle +import ColorPalette + +Item { + id: root + required property BasicLogin loginService + required property PaginatedColorsResource colors + required property PaginatedColorUsersResource colorViewUsers + + ColorDialogEditor { + id: colorPopup + onColorAdded: (colorNameField, colorRGBField, colorPantoneField) => { + root.colors.add({"name" : colorNameField, + "color" : colorRGBField, + "pantone_value" : colorPantoneField}) + } + + onColorUpdated: (colorNameField, colorRGBField, colorPantoneField, cid) => { + root.colors.update({"name" : colorNameField, + "color" : colorRGBField, + "pantone_value" : colorPantoneField}, + cid) + } + } + + ColorDialogDelete { + id: colorDeletePopup + onDeleteClicked: (cid) => { + root.colors.remove(cid) + } + } + + ColumnLayout { + // The main application layout + anchors.fill :parent + + ToolBar { + Layout.fillWidth: true + Layout.minimumHeight: 25 + 4 + + UserMenu { + id: userMenu + + userMenuUsers: root.colorViewUsers + userLoginService: root.loginService + } + + RowLayout { + anchors.fill: parent + Text { + text: qsTr("QHTTP Server") + font.pixelSize: 8 + color: "#667085" + } + Item { Layout.fillWidth: true } + + AbstractButton { + id: loginButton + Layout.preferredWidth: 25 + Layout.preferredHeight: 25 + Item { + id: userImageCliped + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + width: 25 + height: 25 + + Image { + id: userImage + anchors.fill: parent + source: getCurrentUserImage() + visible: false + + function getCurrentUserImage() { + if (root.loginService.loggedIn) + return users.avatarForEmail(loginService.user) + return "qrc:/qt/qml/ColorPalette/icons/user.svg"; + } + } + + Image { + id: userMask + source: "qrc:/qt/qml/ColorPalette/icons/userMask.svg" + anchors.fill: userImage + anchors.margins: 4 + visible: false + } + + MultiEffect { + source: userImage + anchors.fill: userImage + maskSource: userMask + maskEnabled: true + } + } + + onClicked: { + userMenu.open() + var pos = mapToGlobal(Qt.point(x, y)) + pos = userMenu.parent.mapFromGlobal(pos) + userMenu.x = x - userMenu.width + 25 + 3 + userMenu.y = y + 25 + 3 + } + + Shape { + id: bubble + x: -text.width - 25 + anchors.margins: 3 + + preferredRendererType: Shape.CurveRenderer + + visible: !root.loginService.loggedIn + + ShapePath { + strokeWidth: 0 + fillColor: "#667085" + startX: 5; startY: 0 + PathLine { x: 5 + text.width + 6; y: 0 } + PathArc { x: 10 + text.width + 6; y: 5; radiusX: 5; radiusY: 5} + // arrow + PathLine { x: 10 + text.width + 6; y: 8 + text.height / 2 - 6 } + PathLine { x: 10 + text.width + 6 + 6; y: 8 + text.height / 2 } + PathLine { x: 10 + text.width + 6; y: 8 + text.height / 2 + 6} + PathLine { x: 10 + text.width + 6; y: 5 + text.height + 6 } + // end arrow + PathArc { x: 5 + text.width + 6; y: 10 + text.height + 6 ; radiusX: 5; radiusY: 5} + PathLine { x: 5; y: 10 + text.height + 6 } + PathArc { x: 0; y: 5 + text.height + 6 ; radiusX: 5; radiusY: 5} + PathLine { x: 0; y: 5 } + PathArc { x: 5; y: 0 ; radiusX: 5; radiusY: 5} + } + Text { + x: 8 + y: 8 + id: text + color: "white" + text: qsTr("Log in to edit") + font.bold: true + horizontalAlignment: Qt.AlignHCenter + verticalAlignment: Qt.AlignVCenter + } + } + } + } + + Image { + anchors.centerIn: parent + source: "qrc:/qt/qml/ColorPalette/icons/qt.png" + fillMode: Image.PreserveAspectFit + height: 25 + } + + } + ToolBar { + Layout.fillWidth: true + Layout.minimumHeight: 32 + + RowLayout { + anchors.fill: parent + Text { + Layout.alignment: Qt.AlignVCenter + text: qsTr("Color Palette") + font.pixelSize: 14 + font.bold: true + color: "#667085" + } + + Item { Layout.fillWidth: true } + + AbstractButton { + Layout.preferredWidth: 25 + Layout.preferredHeight: 25 + Layout.alignment: Qt.AlignVCenter + + Rectangle { + anchors.fill: parent + radius: 4 + color: "#192CDE85" + border.color: "#DDE2E8" + border.width: 1 + } + + Image { + source: UIStyle.iconPath("plus") + fillMode: Image.PreserveAspectFit + anchors.fill: parent + sourceSize.width: width + sourceSize.height: height + + } + visible: root.loginService.loggedIn + onClicked: colorPopup.createNewColor() + } + + AbstractButton { + Layout.preferredWidth: 25 + Layout.preferredHeight: 25 + Layout.alignment: Qt.AlignVCenter + + Rectangle { + anchors.fill: parent + radius: 4 + color: "#192CDE85" + border.color: "#DDE2E8" + border.width: 1 + } + + Image { + source: UIStyle.iconPath("update") + fillMode: Image.PreserveAspectFit + anchors.fill: parent + sourceSize.width: width + sourceSize.height: height + } + + onClicked: { + root.colors.refreshCurrentPage() + root.colorViewUsers.refreshCurrentPage() + } + } + } + } + + + + //! [View and model] + ListView { + id: colorListView + + model: root.colors.model + //! [View and model] + footerPositioning: ListView.OverlayFooter + spacing: 15 + clip: true + + Layout.fillHeight: true + Layout.fillWidth: true + + header: Rectangle { + height: 32 + width: parent.width + color: "#F0F1F3" + + RowLayout { + anchors.fill: parent + + component HeaderText : Text { + Layout.alignment: Qt.AlignVCenter + horizontalAlignment: Qt.AlignHCenter + + font.pixelSize: 12 + color: "#667085" + } + HeaderText { + id: headerName + text: qsTr("Color Name") + Layout.preferredWidth: colorListView.width * 0.3 + } + HeaderText { + id: headerRgb + text: qsTr("Rgb Value") + Layout.preferredWidth: colorListView.width * 0.25 + } + HeaderText { + id: headerPantone + text: qsTr("Pantone Value") + Layout.preferredWidth: colorListView.width * 0.25 + } + HeaderText { + id: headerAction + text: qsTr("Action") + Layout.preferredWidth: colorListView.width * 0.2 + } + } + } + + delegate: Item { + id: colorInfo + + required property int color_id + required property string name + required property string color + required property string pantone_value + + width: colorListView.width + height: 25 + RowLayout { + anchors.fill: parent + anchors.leftMargin: 5 + anchors.rightMargin: 5 + + Rectangle { + id: colorSample + Layout.alignment: Qt.AlignVCenter + implicitWidth: 36 + implicitHeight: 21 + radius: 6 + color: colorInfo.color + } + + Text { + Layout.preferredWidth: colorInfo.width * 0.3 - colorSample.width + horizontalAlignment: Qt.AlignLeft + leftPadding: 5 + text: colorInfo.name + } + + Text { + Layout.preferredWidth: colorInfo.width * 0.25 + horizontalAlignment: Qt.AlignHCenter + text: colorInfo.color + } + + Text { + Layout.preferredWidth: colorInfo.width * 0.25 + horizontalAlignment: Qt.AlignHCenter + text: colorInfo.pantone_value + } + + Item { + Layout.maximumHeight: 28 + implicitHeight: buttonBox.implicitHeight + implicitWidth: buttonBox.implicitWidth + + RowLayout { + id: buttonBox + anchors.fill: parent + ToolButton { + icon.source: UIStyle.iconPath("delete") + enabled: root.loginService.loggedIn + onClicked: colorDeletePopup.maybeDelete(color_id, name) + } + ToolButton { + icon.source: UIStyle.iconPath("edit") + enabled: root.loginService.loggedIn + onClicked: colorPopup.updateColor(color_id, name, color, pantone_value) + } + } + } + } + } + + footer: ToolBar { + // Paginate buttons if more than one page + visible: root.colors.pages > 1 + implicitWidth: parent.width + + RowLayout { + anchors.fill: parent + + Item { Layout.fillWidth: true /* spacer */ } + + Repeater { + model: root.colors.pages + + ToolButton { + text: page + font.bold: root.colors.page === page + + required property int index + readonly property int page: (index + 1) + + onClicked: root.colors.page = page + } + } + } + } + } + } +} diff --git a/examples/demos/colorpaletteclient/ColorPalette/Main.qml b/examples/demos/colorpaletteclient/ColorPalette/Main.qml new file mode 100644 index 00000000..ae1e8553 --- /dev/null +++ b/examples/demos/colorpaletteclient/ColorPalette/Main.qml @@ -0,0 +1,62 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +pragma ComponentBehavior: Bound + +import QtQuick + +import ColorPalette + +Window { + id: window + width: 500 + height: 400 + visible: true + title: qsTr("Color Palette Client") + + enum DataView { + UserView = 0, + ColorView = 1 + } + + ServerSelection { + id: serverview + anchors.fill: parent + onServerSelected: {colorview.visible = true; serverview.visible = false} + colorResources: colors + restPalette: paletteService + colorUsers: users + } + + ColorView { + id: colorview + anchors.fill: parent + visible: false + loginService: colorLogin + colors: colors + colorViewUsers: users + } + + //! [RestService QML element] + RestService { + id: paletteService + + PaginatedColorUsersResource { + id: users + path: "/api/users" + } + + PaginatedColorsResource { + id: colors + path: "/api/unknown" + } + + BasicLogin { + id: colorLogin + loginPath: "/api/login" + logoutPath: "/api/logout" + } + } + //! [RestService QML element] + +} diff --git a/examples/demos/colorpaletteclient/ColorPalette/ServerSelection.qml b/examples/demos/colorpaletteclient/ColorPalette/ServerSelection.qml new file mode 100644 index 00000000..c170773c --- /dev/null +++ b/examples/demos/colorpaletteclient/ColorPalette/ServerSelection.qml @@ -0,0 +1,241 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import ColorPalette +import QtExampleStyle + +pragma ComponentBehavior: Bound + +Item { + id: root + // A popup for selecting the server URL + + signal serverSelected() + + required property PaginatedColorsResource colorResources + required property PaginatedColorUsersResource colorUsers + required property RestService restPalette + + Connections { + target: root.colorResources + // Closes the URL selection popup once we have received data successfully + function onDataUpdated() { + fetchTester.stop() + root.serverSelected() + } + } + + + ListModel { + id: server + ListElement { + title: qsTr("Public REST API Test Server") + url: "https://reqres.in" + icon: "qrc:/qt/qml/ColorPalette/icons/testserver.png" + } + ListElement { + title: qsTr("Qt-based REST API server") + url: "http://127.0.0.1:49425" + icon: "qrc:/qt/qml/ColorPalette/icons/qt.png" + } + } + + + ColumnLayout { + anchors.fill: parent + anchors.margins: 20 + spacing: 10 + + Image { + Layout.alignment: Qt.AlignHCenter + source: "qrc:/qt/qml/ColorPalette/icons/qt.png" + fillMode: Image.PreserveAspectFit + Layout.preferredWidth: 20 + } + + Label { + text: qsTr("Choose a server") + Layout.alignment: Qt.AlignHCenter + font.pixelSize: 24 + } + + component ServerListDelegate: Rectangle { + id: serverListDelegate + required property string title + required property string url + required property string icon + required property int index + + radius: 10 + color: "#00000000" + + border.color: ListView.view.currentIndex === index ? "#2CDE85" : "#E0E2E7" + border.width: 2 + + implicitWidth: 180 + implicitHeight: 100 + + Rectangle { + id: img + anchors.left: parent.left + anchors.top: parent.top + anchors.topMargin: 10 + anchors.leftMargin: 20 + + width: 30 + height: 30 + radius: 200 + border. color: "#E7F4EE" + border.width: 5 + + Image { + anchors.centerIn: parent + source: serverListDelegate.icon + width: 15 + height: 15 + fillMode: Image.PreserveAspectFit + smooth: true + } + } + + Text { + text: parent.url + + anchors.left: parent.left + anchors.top: img.bottom + anchors.topMargin: 10 + anchors.leftMargin: 20 + color: "#667085" + font.pixelSize: 13 + } + Text { + text: parent.title + + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + anchors.bottomMargin: 10 + color: "#222222" + font.pixelSize: 11 + font.bold: true + } + + MouseArea { + anchors.fill: parent + onClicked: serverList.currentIndex = serverListDelegate.index; + } + } + + ListView { + id: serverList + Layout.alignment: Qt.AlignHCenter + Layout.minimumWidth: 180 * server.count + 20 + Layout.minimumHeight: 100 + orientation: ListView.Horizontal + + model: server + spacing: 20 + + delegate: ServerListDelegate {} + } + + Button { + Layout.alignment: Qt.AlignHCenter + text: restPalette.sslSupported ? qsTr("Connect (SSL)") : qsTr("Connect") + + buttonColor: "#2CDE85" + textColor: "#FFFFFF" + + onClicked: { + busyIndicatorPopup.title = (serverList.currentItem as ServerListDelegate).title + busyIndicatorPopup.icon = (serverList.currentItem as ServerListDelegate).icon + busyIndicatorPopup.open() + + fetchTester.test((serverList.currentItem as ServerListDelegate).url) + } + } + + Timer { + id: fetchTester + interval: 2000 + + function test(url) { + root.restPalette.url = url + root.colorResources.refreshCurrentPage() + root.colorUsers.refreshCurrentPage() + start() + } + onTriggered: busyIndicatorPopup.close() + } + } + + onVisibleChanged: {if (!visible) busyIndicatorPopup.close();} + + Popup { + id: busyIndicatorPopup + padding: 10 + modal: true + focus: true + anchors.centerIn: parent + closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent + + property alias title: titleText.text + property alias icon: titleImg.source + + ColumnLayout { + id: fetchIndicator + anchors.fill: parent + + RowLayout { + Rectangle { + Layout.preferredWidth: 50 + Layout.preferredHeight: 50 + radius: 200 + border. color: "#E7F4EE" + border.width: 5 + + Image { + id: titleImg + anchors.centerIn: parent + width: 25 + height: 25 + fillMode: Image.PreserveAspectFit + } + } + + Label { + id: titleText + text:"" + font.pixelSize: 18 + } + } + + RowLayout { + Layout.fillWidth: false + Layout.alignment: Qt.AlignHCenter + BusyIndicator { + running: visible + Layout.fillWidth: true + } + + Label { + text: qsTr("Testing URL") + font.pixelSize: 18 + } + } + + Button { + Layout.alignment: Qt.AlignHCenter + text: qsTr("Cancel") + onClicked: { + busyIndicatorPopup.close() + } + } + + } + + } +} diff --git a/examples/demos/colorpaletteclient/ColorPalette/UserMenu.qml b/examples/demos/colorpaletteclient/ColorPalette/UserMenu.qml new file mode 100644 index 00000000..6c4b2568 --- /dev/null +++ b/examples/demos/colorpaletteclient/ColorPalette/UserMenu.qml @@ -0,0 +1,139 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Effects + +import QtExampleStyle +import ColorPalette + +Popup { + id: userMenu + + required property BasicLogin userLoginService + required property PaginatedColorUsersResource userMenuUsers + + width: 280 + height: 270 + + ColumnLayout { + anchors.fill: parent + + ListView { + id: userListView + + model: userMenu.userMenuUsers.model + spacing: 5 + footerPositioning: ListView.PullBackFooter + clip: true + + Layout.fillHeight: true + Layout.fillWidth: true + + delegate: Rectangle { + id: userInfo + + required property string email + required property string avatar + + height: 30 + width: userListView.width + + + readonly property bool logged: (email === loginService.user) + + Rectangle { + id: userImageCliped + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + width: 30 + height: 30 + + Image { + id: userImage + anchors.fill: parent + source: userInfo.avatar + visible: false + } + + Image { + id: userMask + source: "qrc:/qt/qml/ColorPalette/icons/userMask.svg" + anchors.fill: userImage + anchors.margins: 4 + visible: false + } + + MultiEffect { + source: userImage + anchors.fill: userImage + maskSource: userMask + maskEnabled: true + } + } + + Text { + id: userMailLabel + anchors.left: userImageCliped.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: 5 + text: userInfo.email + font.bold: userInfo.logged + } + + ToolButton { + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: 5 + + icon.source: UIStyle.iconPath(userInfo.logged + ? "logout" : "login") + enabled: userInfo.logged || !userMenu.userLoginService.loggedIn + + onClicked: { + if (userInfo.logged) { + userMenu.userLoginService.logout() + } else { + //! [Login] + userMenu.userLoginService.login({"email" : userInfo.email, + "password" : "apassword", + "id" : userInfo.id}) + //! [Login] + userMenu.close() + } + } + } + + } + footer: ToolBar { + // Paginate buttons if more than one page + visible: userMenu.userMenuUsers.pages > 1 + implicitWidth: parent.width + + RowLayout { + anchors.fill: parent + + Item { Layout.fillWidth: true /* spacer */ } + + Repeater { + model: userMenu.userMenuUsers.pages + + ToolButton { + text: page + font.bold: userMenu.userMenuUsers.page === page + + required property int index + readonly property int page: (index + 1) + + onClicked: userMenu.userMenuUsers.page = page + } + } + } + } + } + } +} diff --git a/examples/demos/colorpaletteclient/ColorPalette/qmldir b/examples/demos/colorpaletteclient/ColorPalette/qmldir new file mode 100644 index 00000000..7a153fea --- /dev/null +++ b/examples/demos/colorpaletteclient/ColorPalette/qmldir @@ -0,0 +1,7 @@ +module ColorPalette +Main 1.0 Main.qml +ColorDialogDelete 1.0 ColorDialogDelete.qml +ColorDialogEditor 1.0 ColorDialogEditor.qml +ColorView 1.0 ColorView.qml +ServerSelection 1.0 ServerSelection.qml +UserMenu 1.0 UserMenu.qml diff --git a/examples/demos/colorpaletteclient/QtExampleStyle/Button.qml b/examples/demos/colorpaletteclient/QtExampleStyle/Button.qml new file mode 100644 index 00000000..6b3f922a --- /dev/null +++ b/examples/demos/colorpaletteclient/QtExampleStyle/Button.qml @@ -0,0 +1,48 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Controls +import QtQuick.Controls.impl +import QtQuick.Templates as T + +T.Button { + id: control + + property alias buttonColor: rect.color + property alias textColor: label.color + + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, + implicitContentWidth + leftPadding + rightPadding) + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, + implicitContentHeight + topPadding + bottomPadding) + + leftPadding: 15 + rightPadding: 15 + topPadding: 10 + bottomPadding: 10 + + background: Rectangle { + id: rect + radius: 8 + border.color: "#E0E2E7" + border.width: 1 + color: "#FFFFFF" + } + + icon.width: 24 + icon.height: 24 + icon.color: control.palette.buttonText + + contentItem: IconLabel { + id: label + spacing: control.spacing + mirrored: control.mirrored + display: control.display + + icon: control.icon + text: control.text + font.pixelSize: 14 + color: "#667085" + } +} diff --git a/examples/demos/colorpaletteclient/QtExampleStyle/CMakeLists.txt b/examples/demos/colorpaletteclient/QtExampleStyle/CMakeLists.txt new file mode 100644 index 00000000..a911f874 --- /dev/null +++ b/examples/demos/colorpaletteclient/QtExampleStyle/CMakeLists.txt @@ -0,0 +1,54 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(qtexamplestyle LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/quickcontrols/colorpaletteclient/QtExampleStyle") + +find_package(Qt6 REQUIRED COMPONENTS Core Gui Quick QuickControls2) + +set_source_files_properties(UIStyle.qml + PROPERTIES + QT_QML_SINGLETON_TYPE TRUE +) + +qt_policy(SET QTP0001 NEW) +qt_add_qml_module(qtexamplestyle + URI QtExampleStyle + PLUGIN_TARGET qtexamplestyle + QML_FILES + Button.qml + Popup.qml + UIStyle.qml + TextField.qml +) + +target_link_libraries(qtexamplestyle PUBLIC + Qt6::Core + Qt6::Gui + Qt6::Quick + Qt6::QuickControls2 +) + +if(UNIX AND NOT APPLE AND CMAKE_CROSSCOMPILING) + find_package(Qt6 REQUIRED COMPONENTS QuickTemplates2) + + # Work around QTBUG-86533 + target_link_libraries(qtexamplestyle PRIVATE Qt6::QuickTemplates2) +endif() + +install(TARGETS qtexamplestyle + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/qmldir + DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/examples/demos/colorpaletteclient/QtExampleStyle/Popup.qml b/examples/demos/colorpaletteclient/QtExampleStyle/Popup.qml new file mode 100644 index 00000000..a3132bce --- /dev/null +++ b/examples/demos/colorpaletteclient/QtExampleStyle/Popup.qml @@ -0,0 +1,27 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Templates as T + +T.Popup { + id: control + + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, + implicitContentWidth + leftPadding + rightPadding) + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, + implicitContentHeight + topPadding + bottomPadding) + + leftPadding: 15 + rightPadding: 15 + topPadding: 10 + bottomPadding: 10 + + background: Rectangle { + id: bg + radius: 8 + border.color: "#E0E2E7" + border.width: 2 + color: "#FFFFFF" + } +} diff --git a/examples/demos/colorpaletteclient/QtExampleStyle/TextField.qml b/examples/demos/colorpaletteclient/QtExampleStyle/TextField.qml new file mode 100644 index 00000000..7db2d4f9 --- /dev/null +++ b/examples/demos/colorpaletteclient/QtExampleStyle/TextField.qml @@ -0,0 +1,22 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Templates as T + +T.TextField { + id: control + placeholderText: "" + + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, contentWidth + leftPadding + rightPadding) + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, + contentHeight + topPadding + bottomPadding) + + background: Rectangle { + implicitWidth: 200 + implicitHeight: 40 + radius: 8 + color: control.enabled ? "transparent" : "#353637" + border.color: "#E0E2E7" + } +} diff --git a/examples/demos/colorpaletteclient/QtExampleStyle/UIStyle.qml b/examples/demos/colorpaletteclient/QtExampleStyle/UIStyle.qml new file mode 100644 index 00000000..3c4741d7 --- /dev/null +++ b/examples/demos/colorpaletteclient/QtExampleStyle/UIStyle.qml @@ -0,0 +1,29 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +pragma Singleton + +import QtQuick + +QtObject { + id: uiStyle + + // Font Sizes + readonly property int fontSizeXXS: 10 + readonly property int fontSizeXS: 15 + readonly property int fontSizeS: 20 + readonly property int fontSizeM: 25 + readonly property int fontSizeL: 30 + readonly property int fontSizeXL: 35 + readonly property int fontSizeXXL: 40 + + // Color Scheme + // Green + readonly property color colorQtPrimGreen: "#41cd52" + readonly property color colorQtAuxGreen1: "#21be2b" + readonly property color colorQtAuxGreen2: "#17a81a" + + function iconPath(baseImagePath) { + return `qrc:/qt/qml/ColorPalette/icons/${baseImagePath}.svg` + } +} diff --git a/examples/demos/colorpaletteclient/QtExampleStyle/qmldir b/examples/demos/colorpaletteclient/QtExampleStyle/qmldir new file mode 100644 index 00000000..7bdfb44d --- /dev/null +++ b/examples/demos/colorpaletteclient/QtExampleStyle/qmldir @@ -0,0 +1,5 @@ +module QtExampleStyle +Button 1.0 Button.qml +Popup 1.0 Popup.qml +TextField 1.0 TextField.qml +singleton UIStyle 1.0 UIStyle.qml diff --git a/examples/demos/colorpaletteclient/abstractresource.py b/examples/demos/colorpaletteclient/abstractresource.py new file mode 100644 index 00000000..3f3a7ed6 --- /dev/null +++ b/examples/demos/colorpaletteclient/abstractresource.py @@ -0,0 +1,24 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from PySide6.QtCore import QObject +from PySide6.QtQml import QmlAnonymous + + +QML_IMPORT_NAME = "ColorPalette" +QML_IMPORT_MAJOR_VERSION = 1 + + +@QmlAnonymous +class AbstractResource(QObject): + + def __init__(self, parent=None): + super().__init__(parent) + self.m_manager = None # QRestAccessManager + self.m_api = None # QNetworkRequestFactory + + def setAccessManager(self, manager): + self.m_manager = manager + + def setServiceApi(self, serviceApi): + self.m_api = serviceApi diff --git a/examples/demos/colorpaletteclient/basiclogin.py b/examples/demos/colorpaletteclient/basiclogin.py new file mode 100644 index 00000000..b9139c2e --- /dev/null +++ b/examples/demos/colorpaletteclient/basiclogin.py @@ -0,0 +1,100 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import sys +from functools import partial +from dataclasses import dataclass + +from PySide6.QtCore import Property, Signal, Slot +from PySide6.QtNetwork import QHttpHeaders +from PySide6.QtQml import QmlElement + +from abstractresource import AbstractResource + + +tokenField = "token" +emailField = "email" +idField = "id" + + +QML_IMPORT_NAME = "ColorPalette" +QML_IMPORT_MAJOR_VERSION = 1 + + +@QmlElement +class BasicLogin(AbstractResource): + @dataclass + class User: + email: str + token: bytes + id: int + + userChanged = Signal() + + def __init__(self, parent=None): + super().__init__(parent) + self.m_user = None + self.m_loginPath = "" + self.m_logoutPath = "" + self.m_user = None + + @Property(str, notify=userChanged) + def user(self): + return self.m_user.email if self.m_user else "" + + @Property(bool, notify=userChanged) + def loggedIn(self): + return bool(self.m_user) + + @Property(str) + def loginPath(self): + return self.m_loginPath + + @loginPath.setter + def loginPath(self, p): + self.m_loginPath = p + + @Property(str) + def logoutPath(self): + return self.m_logoutPath + + @logoutPath.setter + def logoutPath(self, p): + self.m_logoutPath = p + + @Slot("QVariantMap") + def login(self, data): + request = self.m_api.createRequest(self.m_loginPath) + self.m_manager.post(request, data, self, partial(self.loginReply, data)) + + def loginReply(self, data, reply): + self.m_user = None + if not reply.isSuccess(): + print("login: ", reply.errorString(), file=sys.stderr) + (json, error) = reply.readJson() + if json and json.isObject(): + json_object = json.object() + token = json_object.get(tokenField) + if token: + email = data[emailField] + token = json_object[tokenField] + id = data[idField] + self.m_user = BasicLogin.User(email, token, id) + + headers = QHttpHeaders() + headers.append("token", self.m_user.token if self.m_user else "") + self.m_api.setCommonHeaders(headers) + self.userChanged.emit() + + @Slot() + def logout(self): + request = self.m_api.createRequest(self.m_logoutPath) + self.m_manager.post(request, b"", self, self.logoutReply) + + def logoutReply(self, reply): + if reply.isSuccess(): + self.m_user = None + self.m_api.clearCommonHeaders() # clears 'token' header + self.userChanged.emit() + else: + print("logout: ", reply.errorString(), file=sys.stderr) diff --git a/examples/demos/colorpaletteclient/colorpaletteclient.pyproject b/examples/demos/colorpaletteclient/colorpaletteclient.pyproject new file mode 100644 index 00000000..d05f7cb2 --- /dev/null +++ b/examples/demos/colorpaletteclient/colorpaletteclient.pyproject @@ -0,0 +1,21 @@ +{ + "files": [ + "abstractresource.py", + "basiclogin.py", + "main.py", + "paginatedresource.py", + "restservice.py", + "colorpaletteclient.qrc", + "ColorPalette/ColorDialogDelete.qml", + "ColorPalette/ColorDialogEditor.qml", + "ColorPalette/ColorView.qml", + "ColorPalette/Main.qml", + "ColorPalette/ServerSelection.qml", + "ColorPalette/UserMenu.qml", + "QtExampleStyle/Button.qml", + "QtExampleStyle/Popup.qml", + "QtExampleStyle/TextField.qml", + "QtExampleStyle/UIStyle.qml", + "colorpaletteclient.qrc" + ] +} diff --git a/examples/demos/colorpaletteclient/colorpaletteclient.qrc b/examples/demos/colorpaletteclient/colorpaletteclient.qrc new file mode 100644 index 00000000..16260cbd --- /dev/null +++ b/examples/demos/colorpaletteclient/colorpaletteclient.qrc @@ -0,0 +1,17 @@ + + + icons/close.svg + icons/delete.svg + icons/dots.svg + icons/edit.svg + icons/login.svg + icons/logout.svg + icons/ok.svg + icons/plus.svg + icons/qt.png + icons/testserver.png + icons/update.svg + icons/user.svg + icons/userMask.svg + + diff --git a/examples/demos/colorpaletteclient/doc/colorpaletteclient.rst b/examples/demos/colorpaletteclient/doc/colorpaletteclient.rst new file mode 100644 index 00000000..0dcb91d4 --- /dev/null +++ b/examples/demos/colorpaletteclient/doc/colorpaletteclient.rst @@ -0,0 +1,79 @@ +RESTful API client +================== + +Example of how to create a RESTful API QML client. + +This example shows how to create a basic QML RESTful API client with an +imaginary color palette service. The application uses RESTful communication +with the selected server to request and send data. The REST service is provided +as a QML element whose child elements wrap the individual JSON data APIs +provided by the server. + +Application functionality +------------------------- + +The example provides the following basic functionalities: +* Select the server to communicate with +* List users and colors +* Login and logout users +* Modify and create new colors + +Server selection +---------------- + +At start the application presents the options for the color palette server to communicate +with. The predefined options are: + +* ``https://reqres.in``, a publicly available REST API test service +* A Qt-based REST API server example in ``QtHttpServer`` + +Once selected, the RESTful API client issues a test HTTP GET to the color API +to check if the service is accessible. + +One major difference between the two predefined API options is that the +Qt-based REST API server example is a stateful application which allows +modifying colors, whereas the ``reqres.in`` is a stateless API testing service. +In other words, when using the ``reqres.in`` backend, modifying the colors has +no lasting impact. + +The users and colors are paginated resources on the server-side. This means +that the server provides the data in chunks called pages. The UI listing +reflects this pagination and views the data on pages. + +Viewing the data on UI is done with standard QML views where the model are +QAbstractListModel-derived classes representing JSON data received from the +server. + +Logging in happens via the login function provided by the login popup. Under +the hood the login sends a HTTP POST request. Upon receiving a successful +response the authorization token is extracted from the response, which in turn +is then used in subsequent HTTP requests which require the token. + +Editing and adding new colors is done in a popup. Note that uploading the color +changes to the server requires that a user has logged in. + +REST implementation +------------------- + +The example illustrates one way to compose a REST service from individual resource elements. In +this example the resources are the paginated user and color resources plus the login service. +The resource elements are bound together by the base URL (server URL) and the shared network access +manager. + +The basis of the REST service is the RestService QML element whose children items +compose the actual service. + +Upon instantiation the RestService element loops its children elements and sets +them up to use the same network access manager. This way the individual +resources share the same access details such as the server URL and +authorization token. + +The actual communication is done with a rest access manager which implements +some convenience functionality to deal specifically with HTTP REST APIs and +effectively deals with sending and receiving the ``QNetworkRequest`` and +``QNetworkReply`` as needed. + +.. image:: colorpaletteclient.webp + :width: 90% + :align: center + :alt: RESTful API client diff --git a/examples/demos/colorpaletteclient/doc/colorpaletteclient.webp b/examples/demos/colorpaletteclient/doc/colorpaletteclient.webp new file mode 100644 index 00000000..8f4d9a62 Binary files /dev/null and b/examples/demos/colorpaletteclient/doc/colorpaletteclient.webp differ diff --git a/examples/demos/colorpaletteclient/icons/close.svg b/examples/demos/colorpaletteclient/icons/close.svg new file mode 100644 index 00000000..3a0d4be6 --- /dev/null +++ b/examples/demos/colorpaletteclient/icons/close.svg @@ -0,0 +1,3 @@ + + + diff --git a/examples/demos/colorpaletteclient/icons/delete.svg b/examples/demos/colorpaletteclient/icons/delete.svg new file mode 100644 index 00000000..8f04948c --- /dev/null +++ b/examples/demos/colorpaletteclient/icons/delete.svg @@ -0,0 +1,3 @@ + + + diff --git a/examples/demos/colorpaletteclient/icons/dots.svg b/examples/demos/colorpaletteclient/icons/dots.svg new file mode 100644 index 00000000..49df163f --- /dev/null +++ b/examples/demos/colorpaletteclient/icons/dots.svg @@ -0,0 +1,3 @@ + + + diff --git a/examples/demos/colorpaletteclient/icons/edit.svg b/examples/demos/colorpaletteclient/icons/edit.svg new file mode 100644 index 00000000..1cfc2a73 --- /dev/null +++ b/examples/demos/colorpaletteclient/icons/edit.svg @@ -0,0 +1,3 @@ + + + diff --git a/examples/demos/colorpaletteclient/icons/login.svg b/examples/demos/colorpaletteclient/icons/login.svg new file mode 100644 index 00000000..c8fe5bc5 --- /dev/null +++ b/examples/demos/colorpaletteclient/icons/login.svg @@ -0,0 +1,3 @@ + + + diff --git a/examples/demos/colorpaletteclient/icons/logout.svg b/examples/demos/colorpaletteclient/icons/logout.svg new file mode 100644 index 00000000..91d4fd86 --- /dev/null +++ b/examples/demos/colorpaletteclient/icons/logout.svg @@ -0,0 +1,3 @@ + + + diff --git a/examples/demos/colorpaletteclient/icons/ok.svg b/examples/demos/colorpaletteclient/icons/ok.svg new file mode 100644 index 00000000..506e2d69 --- /dev/null +++ b/examples/demos/colorpaletteclient/icons/ok.svg @@ -0,0 +1,3 @@ + + + diff --git a/examples/demos/colorpaletteclient/icons/plus.svg b/examples/demos/colorpaletteclient/icons/plus.svg new file mode 100644 index 00000000..81837784 --- /dev/null +++ b/examples/demos/colorpaletteclient/icons/plus.svg @@ -0,0 +1,3 @@ + + + diff --git a/examples/demos/colorpaletteclient/icons/qt.png b/examples/demos/colorpaletteclient/icons/qt.png new file mode 100644 index 00000000..abd3a4f1 Binary files /dev/null and b/examples/demos/colorpaletteclient/icons/qt.png differ diff --git a/examples/demos/colorpaletteclient/icons/qt_attribution.json b/examples/demos/colorpaletteclient/icons/qt_attribution.json new file mode 100644 index 00000000..44633c47 --- /dev/null +++ b/examples/demos/colorpaletteclient/icons/qt_attribution.json @@ -0,0 +1,14 @@ +{ + "Id": "colorpaletteclient", + "Name": "Selected Material Icons", + "QDocModule": "qtdoc", + "QtUsage": "Used in Color Palette Client example in QtDoc", + "QtParts": [ + "examples" + ], + "Files": "close.svg delete.svg dots.svg edit.svg login.svg logout.svg ok.svg update.svg user.svg", + "Homepage": "https://fonts.google.com/icons", + "License": "Apache License Version 2.0", + "LicenseId": "Apache-2.0", + "Copyright": "Copyright 2018 Google, Inc. All Rights Reserved." +} diff --git a/examples/demos/colorpaletteclient/icons/testserver.png b/examples/demos/colorpaletteclient/icons/testserver.png new file mode 100644 index 00000000..0890e5e4 Binary files /dev/null and b/examples/demos/colorpaletteclient/icons/testserver.png differ diff --git a/examples/demos/colorpaletteclient/icons/update.svg b/examples/demos/colorpaletteclient/icons/update.svg new file mode 100644 index 00000000..303ff4d3 --- /dev/null +++ b/examples/demos/colorpaletteclient/icons/update.svg @@ -0,0 +1,3 @@ + + + diff --git a/examples/demos/colorpaletteclient/icons/user.svg b/examples/demos/colorpaletteclient/icons/user.svg new file mode 100644 index 00000000..ed782385 --- /dev/null +++ b/examples/demos/colorpaletteclient/icons/user.svg @@ -0,0 +1,4 @@ + + + + diff --git a/examples/demos/colorpaletteclient/icons/userMask.svg b/examples/demos/colorpaletteclient/icons/userMask.svg new file mode 100644 index 00000000..5e3065d7 --- /dev/null +++ b/examples/demos/colorpaletteclient/icons/userMask.svg @@ -0,0 +1,3 @@ + + + diff --git a/examples/demos/colorpaletteclient/main.py b/examples/demos/colorpaletteclient/main.py new file mode 100644 index 00000000..a249b9fa --- /dev/null +++ b/examples/demos/colorpaletteclient/main.py @@ -0,0 +1,33 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +"""PySide6 port of the Qt RESTful API client demo from Qt v6.x""" + +import os +import sys +from pathlib import Path + +from PySide6.QtCore import QUrl +from PySide6.QtGui import QIcon, QGuiApplication +from PySide6.QtQml import QQmlApplicationEngine + +from basiclogin import BasicLogin # noqa: F401 +from paginatedresource import PaginatedResource # noqa: F401 +from restservice import RestService # noqa: F401 +import rc_colorpaletteclient # noqa: F401 + +if __name__ == "__main__": + app = QGuiApplication(sys.argv) + QIcon.setThemeName("colorpaletteclient") + + engine = QQmlApplicationEngine() + app_dir = Path(__file__).parent + app_dir_url = QUrl.fromLocalFile(os.fspath(app_dir)) + engine.addImportPath(os.fspath(app_dir)) + engine.loadFromModule("ColorPalette", "Main") + if not engine.rootObjects(): + sys.exit(-1) + + ex = app.exec() + del engine + sys.exit(ex) diff --git a/examples/demos/colorpaletteclient/paginatedresource.py b/examples/demos/colorpaletteclient/paginatedresource.py new file mode 100644 index 00000000..b7f036c4 --- /dev/null +++ b/examples/demos/colorpaletteclient/paginatedresource.py @@ -0,0 +1,278 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import sys +from dataclasses import dataclass +from PySide6.QtCore import (QAbstractListModel, QByteArray, + QUrlQuery, Property, Signal, Slot, Qt) +from PySide6.QtQml import QmlAnonymous, QmlElement + +from abstractresource import AbstractResource + + +QML_IMPORT_NAME = "ColorPalette" +QML_IMPORT_MAJOR_VERSION = 1 + + +totalPagesField = "total_pages" +currentPageField = "page" + + +@dataclass +class ColorUser: + id: int + email: str + avatar: str # URL + + +@QmlElement +class ColorUserModel (QAbstractListModel): + IdRole = Qt.UserRole + 1 + EmailRole = Qt.UserRole + 2 + AvatarRole = Qt.UserRole + 3 + + def __init__(self, parent=None): + super().__init__(parent) + self._users = [] + + def clear(self): + self.set_data([]) + + def set_data(self, json_list): + if not self._users and not json_list: + return + self.beginResetModel() + self._users.clear() + for e in json_list: + self._users.append(ColorUser(int(e["id"]), e["email"], e["avatar"])) + self.endResetModel() + + def roleNames(self): + roles = { + ColorUserModel.IdRole: QByteArray(b'id'), + ColorUserModel.EmailRole: QByteArray(b'email'), + ColorUserModel.AvatarRole: QByteArray(b'avatar') + } + return roles + + def rowCount(self, index): + return len(self._users) + + def data(self, index, role): + if index.isValid(): + d = self._users[index.row()] + if role == ColorUserModel.IdRole: + return d.id + if role == ColorUserModel.EmailRole: + return d.email + if role == ColorUserModel.AvatarRole: + return d.avatar + return None + + def avatarForEmail(self, email): + for e in self._users: + if e.email == email: + return e.avatar + return "" + + +@dataclass +class Color: + id: int + color: str + name: str + pantone_value: str + + +@QmlElement +class ColorModel (QAbstractListModel): + IdRole = Qt.UserRole + 1 + ColorRole = Qt.UserRole + 2 + NameRole = Qt.UserRole + 3 + PantoneValueRole = Qt.UserRole + 4 + + def __init__(self, parent=None): + super().__init__(parent) + self._colors = [] + + def clear(self): + self.set_data([]) + + def set_data(self, json_list): + if not self._colors and not json_list: + return + self.beginResetModel() + self._colors.clear() + for e in json_list: + self._colors.append(Color(int(e["id"]), e["color"], + e["name"], e["pantone_value"])) + self.endResetModel() + + def roleNames(self): + roles = { + ColorModel.IdRole: QByteArray(b'color_id'), + ColorModel.ColorRole: QByteArray(b'color'), + ColorModel.NameRole: QByteArray(b'name'), + ColorModel.PantoneValueRole: QByteArray(b'pantone_value') + } + return roles + + def rowCount(self, index): + return len(self._colors) + + def data(self, index, role): + if index.isValid(): + d = self._colors[index.row()] + if role == ColorModel.IdRole: + return d.id + if role == ColorModel.ColorRole: + return d.color + if role == ColorModel.NameRole: + return d.name + if role == ColorModel.PantoneValueRole: + return d.pantone_value + return None + + +@QmlAnonymous +class PaginatedResource(AbstractResource): + """This class manages a simple paginated Crud resource, + where the resource is a paginated list of JSON items.""" + + dataUpdated = Signal() + pageUpdated = Signal() + pagesUpdated = Signal() + + def __init__(self, parent=None): + super().__init__(parent) + # The total number of pages as reported by the server responses + self.m_pages = 0 + # The default page we request if the user hasn't set otherwise + self.m_currentPage = 1 + self.m_path = "" + + def _clearModel(self): + pass + + def _populateModel(self, json_list): + pass + + @Property(str) + def path(self): + return self.m_path + + @path.setter + def path(self, p): + self.m_path = p + + @Property(int, notify=pagesUpdated) + def pages(self): + return self.m_pages + + @Property(int, notify=pageUpdated) + def page(self): + return self.m_currentPage + + @page.setter + def page(self, page): + if self.m_currentPage == page or page < 1: + return + self.m_currentPage = page + self.pageUpdated.emit() + self.refreshCurrentPage() + + @Slot() + def refreshCurrentPage(self): + query = QUrlQuery() + query.addQueryItem("page", str(self.m_currentPage)) + request = self.m_api.createRequest(self.m_path, query) + self.m_manager.get(request, self, self.refreshCurrentPageReply) + + def refreshCurrentPageReply(self, reply): + if not reply.isSuccess(): + print("PaginatedResource: ", reply.errorString(), file=sys.stderr) + (json, error) = reply.readJson() + if json: + self.refreshRequestFinished(json) + else: + self.refreshRequestFailed() + + def refreshRequestFinished(self, json): + json_object = json.object() + self._populateModel(json_object["data"]) + self.m_pages = int(json_object[totalPagesField]) + self.m_currentPage = int(json_object[currentPageField]) + self.pageUpdated.emit() + self.pagesUpdated.emit() + self.dataUpdated.emit() + + def refreshRequestFailed(self): + if self.m_currentPage != 1: + # A failed refresh. If we weren't on page 1, try that. + # Last resource on currentPage might have been deleted, causing a failure + self.setPage(1) + else: + # Refresh failed and we we're already on page 1 => clear data + self.m_pages = 0 + self.pagesUpdated.emit() + self._clearModel() + self.dataUpdated.emit() + + @Slot("QVariantMap", int) + def update(self, data, id): + request = self.m_api.createRequest(f"{self.m_path}/{id}") + self.m_manager.put(request, self, self.updateReply) + + def updateReply(self, reply): + if reply.isSuccess(): + self.refreshCurrentPage() + + @Slot("QVariantMap") + def add(self, data): + request = self.m_api.createRequest(self.m_path) + self.m_manager.post(request, data, self, self.updateReply) + + @Slot(int) + def remove(self, id): + request = self.m_api.createRequest(f"{self.m_path}/{id}") + self.m_manager.deleteResource(request, self, self.updateReply) + + +@QmlElement +class PaginatedColorUsersResource(PaginatedResource): + + def __init__(self, parent=None): + super().__init__(parent) + self.m_model = ColorUserModel(self) + + @Property(ColorUserModel, constant=True) + def model(self): + return self.m_model + + def _clearModel(self): + self.m_model.clear() + + def _populateModel(self, json_list): + self.m_model.set_data(json_list) + + @Slot(str, result=str) + def avatarForEmail(self, email): + return self.m_model.avatarForEmail(email) + + +@QmlElement +class PaginatedColorsResource(PaginatedResource): + + def __init__(self, parent=None): + super().__init__(parent) + self.m_model = ColorModel(self) + + @Property(ColorModel, constant=True) + def model(self): + return self.m_model + + def _clearModel(self): + self.m_model.clear() + + def _populateModel(self, json_list): + self.m_model.set_data(json_list) diff --git a/examples/demos/colorpaletteclient/rc_colorpaletteclient.py b/examples/demos/colorpaletteclient/rc_colorpaletteclient.py new file mode 100644 index 00000000..74b3eaf1 --- /dev/null +++ b/examples/demos/colorpaletteclient/rc_colorpaletteclient.py @@ -0,0 +1,1098 @@ +# Resource object code (Python 3) +# Created by: object code +# Created by: The Resource Compiler for Qt version 6.7.0 +# WARNING! All changes made in this file will be lost! + +from PySide6 import QtCore + +qt_resource_data = b"\ +\x00\x00\x00\xc0\ +<\ +svg width=\x2248\x22 h\ +eight=\x2248\x22 viewB\ +ox=\x220 0 48 48\x22 f\ +ill=\x22none\x22 xmlns\ +=\x22http://www.w3.\ +org/2000/svg\x22>\x0a<\ +path d=\x22M22.5 38\ +V25.5H10V22.5H22\ +.5V10H25.5V22.5H\ +38V25.5H25.5V38H\ +22.5Z\x22 fill=\x22#66\ +7085\x22/>\x0a\x0a\ +\x00\x00\x00\x94\ +<\ +svg width=\x2230\x22 h\ +eight=\x2230\x22 viewB\ +ox=\x220 0 30 30\x22 x\ +mlns=\x22http://www\ +.w3.org/2000/svg\ +\x22>\x0a\x0a\x0a\x0a\ +\x00\x00\x0b\x93\ +\x89\ +PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ +\x00\x00d\x00\x00\x00H\x08\x06\x00\x00\x00\x00\x8cP\x19\ +\x00\x00\x01\x84iCCPICC prof\ +ile\x00\x00(\x91}\x91=H\xc3@\x1c\xc5_\ +S\xa5*-\x0ev\x90\xe2\x90\xa1:Y\x10\x15q\xd4\ +*\x14\xa1B\xa8\x15Zu0\xb9\xf4\x0b\x9a\x18\x92\x14\ +\x17G\xc1\xb5\xe0\xe0\xc7b\xd5\xc1\xc5YW\x07WA\ +\x10\xfc\x00quqRt\x91\x12\xff\x97\x14Z\xc4x\ +p\xdc\x8fw\xf7\x1ew\xef\x00\xa1Qe\x9a\xd55\x06\ +h\xbamfRI1\x97_\x11C\xaf\x08#\x82^\ +\xc4\x11\x93\x99e\xccJR\x1a\xbe\xe3\xeb\x1e\x01\xbe\xde\ +%x\x96\xff\xb9?GD-X\x0c\x08\x88\xc43\xcc\ +0m\xe2u\xe2\xa9M\xdb\xe0\xbcO\x1ceeY%\ +>'\x1e5\xe9\x82\xc4\x8f\x5cW<~\xe3\x5crY\ +\xe0\x99Q3\x9b\x99#\x8e\x12\x8b\xa5\x0eV:\x98\x95\ +M\x8dx\x928\xaej:\xe5\x0b9\x8fU\xce[\x9c\ +\xb5j\x8d\xb5\xee\xc9_\x18.\xe8\xcbK\x5c\xa79\x84\ +\x14\x16\xb0\x08\x09\x22\x14\xd4PA\x156\x12\xb4\xea\xa4\ +X\xc8\xd0~\xd2\xc7\x1fs\xfd\x12\xb9\x14rU\xc0\xc8\ +1\x8f\x0dh\x90]?\xf8\x1f\xfc\xee\xd6*N\x8c{\ +I\xe1$\xd0\xfd\xe28\x1f\xc3@h\x17h\xd6\x1d\xe7\ +\xfb\xd8q\x9a'@\xf0\x19\xb8\xd2\xdb\xfe\x8d\x060\xfd\ +Iz\xbd\xad\xc5\x8f\x80\xfem\xe0\xe2\xba\xad){\xc0\ +\xe5\x0e0\xf8d\xc8\xa6\xecJA\x9aB\xb1\x08\xbc\x9f\ +\xd17\xe5\x81\x81[\xa0o\xd5\xeb\xad\xb5\x8f\xd3\x07 \ +K]\xa5o\x80\x83C`\xa4D\xd9k>\xef\xee\xe9\ +\xec\xed\xdf3\xad\xfe~\x00a\xaer\xa0\xbc\xa9O\xc0\ +\x00\x00\x00\x06bKGD\x00\xff\x00\xff\x00\xff\xa0\xbd\ +\xa7\x93\x00\x00\x00\x09pHYs\x00\x00.#\x00\x00\ +.#\x01x\xa5?v\x00\x00\x00\x07tIME\x07\ +\xe7\x0c\x0d\x09$9Q3\xe6g\x00\x00\x00\x19tE\ +XtComment\x00Create\ +d with GIMPW\x81\x0e\x17\x00\ +\x00\x09kIDATx\xda\xed]ilT\xd7\x19\ +=\xe7\xbe\x99\xb1\xcd\x1aC\xc0\xac\xb6\xb1\x81\xda\x1eC\ +T\x11\x10\xa4i\x135\x91\x12E\xa4KT\x14\xda&\ +$]\x14W(\xa9\x02\x81\x006\x84Q\xf0B\x96\x06\ +*D\xd5\xf6\x17\x0a\xaa\xda\x84F)R\xc2\xa2$\x85\ +&M\xd3\x85%\xc2cV\x8f1aKR\xb0qX\ +\xec\x99y\xef~\xfdAAN\xeb\xfb\xbc\xf0f\xf0x\ +\xe6\xfe\x9b\xf9\xde\xdc\xef\xbe{\xde\xf9\xb6{\xef\x1b\xa2\ +\x87\xad\xbc\xf9\x85Rh{'\x80\xf1\xc8\xb4\x844\x81\ +\x08{\x04F\xa4\xfav\xa1\xbcA\xa8\xfc\xcc\xb4%\x12\ +\x10\xe8n\x01\x09\x9e\xa8-\xa3-;AL\xc8LY\ +\xe2\x01Q\xdd1\x03\x8e\xb3=\x03F\xf2\x9a\x91!e\ +\xc7\xd6\x06\x95\xa5w\x00\x92\x01\xe3f3$\x18y~\ +&\xad\xf8\xb6\x0c\x18\xfd\x80!e'\xd6\x06\x95\xed\xec\ +\x043\xd1\xd4MgH0\xf2\xfcL\xda\xf6\xf6\x0c\x18\ +\xfd\x80!\xa5\x8d5\xe5\x16\xb1#\x03F?`H0\ +R7SQ\xb6e\xc0\xe8\x07\x0c)\xfd\xa4\xa6\x5c\xd9\ +\xd8I`\xdcM}:D\x84\x8a\xed\x10DE\xc4\x01\ +\x00\x92>\x00\xd9\x02\xc9\x22\xc8t`\x88O\xd9\xfam\ +B\xdd\x0c0: \xd8\x01\xca\x87\x8e\xb6\x22>\xb2\xd1\ +\xa6s\xae\xfdB\xbc}\xd8\xd8\xc1q\x00\xf8\xe2\xece\ +\x7fN\xae?'\x00=&\xae\xd5d\x05N\x15p6\ +!w\x01\x18:\x10A\xf1\x11jB\x12i\x00\x90{\ +\x04\xd8t\x99\xf1\xdf5\x17\x85.\xf4\xe0W\x17\x00\x9c\ +\x05\xb0\xffz\xc2\xdaT\x93G\xe1\x0f5\xf4\xcfHN\ +\x19P&\xab\xbc\xa9\xc6\x01\xa0\x92\x10=\xb4A\xb8(\ +\x90\x13{m\xef\xb8\xd0\x15/\xfa\x9c|\xec\xa9\xac,\ +k\xecRB\x9e\x060r \x98\xac\xe4\x00B\x1c\x12\ +\xc5\xef5\x14T\x1eLD\xf7\xc1\xc6\xda\xafQ\xc9\xab\ +\x00\x8az9\x03\xbfQ>l\xe9J\xe4h\xf5\x14E\ +\x7f;\xe9>$\x09Z\xc2v4\xfe\xe0\xe1\xd2Ps\ +\xa2T4L\xae\xfc\xb04\xb2\xe6~K\xa9w (\ +\xe8\xf9\xd0\xe4\xf0\x81\x82\x95\xefu]\xad\xa8y\x087\ +!\x8cP\x09\x06\xe3\x1c\xfc\xbe\x1f%\x12\x8ck\xedP\ +\xf1\xaacZ\xe1'W\x1dU\x0a;\xf5\xc4Z*\xd9\ +P\x9f\xbflOw\xd7\x956U\x17(\x8d\x87I\x94\ +\x80\x9c\x00\x91\x5c\x90\x1a@\xab\x10'\xe8H\x03rr\ +\xde\x0c\x8f\x7f\xe6\xa4[?\x07\x0b\xaa\xde\x9bv\xbc\xfa\ +%\x11<\x9b\x01\xe4\xff\xd1\x88\x5c\xa4\xfd\xa2\xdb%e\ +\x91\xd5\xf9\x14\xff*\x80?\xa6\xea\xc4\xd6N)\x07\x05\ +\x80\x22$\xda\xb1\xa6\x92\xbf\xec\x8ch\xbc\x91\ +\xaa\x0cI\x0c \xe4yZ\xd9\x9bL\xe2\xf2\xa6\xf5y\ +\xb6\x92\xcd\x10)\xea=\xf10U\xfc\xfe\xcd\x08\x85\x8c\ +c\x8f_\xb17^\x0dZ2\x80\xfc\xd7\x99\xebm\x0d\ +\x13\x17\xb7\x98\xf1\xbaRAJ\xf0\x06\x82\x85;\xca\x17\ +\xf8\x17\x99\xc4G\xa7\x87\x0e\x83\xf8G\x06\x90k\x13\xae\ +\xf5\x9fM\xb2\x92C+F\x8a\xc8\x93\x1e\xa8y\xa4\xac\ +!\x140\x9aL-[3N\xfd*\x1cq\xfa\xf8\x17\ +\xa3B\xdf\xe0\x07\x00\x8c\xf2\x80\x86\xb7I\x8eo6\x80\ +\xf7\xbb\x1c\x85e\xfd\x13Z\x0b:/\xc2i\xfc\x09\xc4\ +\xc7\xd7>Z\x1a\x7f7>\xa9\x16\xdf\x16G>7j\ +W\x14\x8a~\x04\xf0\xb6t\x93\x00@t\xd3C\x05\xce\ +\x89\x03&\xb1\x85\xbb\xbcrT\x8ab\x04$\x1e\xf5\x1f\ +\x08\xf8;\xda\x00\xder}\x12\xfd\xd6\x9b\x0d\x05\xcb_\ +\xedI\xef\xf5\x85\x95\xdb\x00ls\xad\x104U\xcf!\ +0\xa5_\x9b,R\xed\x0b1\xa4]\xae\xb8\xd3\xbb\xc8\ +\x9a\xb3\x8d~d\xea\x92\xf3\x00\x1b\xd3\xde\x87h\xc8'\ +&\xd9\xf4OC\xa3\x05\xe2\xd9\x13%\x22s\xdc\xb2R\ +\x11}4\xed\x01\x11\x91SFY{\xa0\x94\xa0\xf2\x8e\ +\x8d\xcc\x9bu\xa8\xd6\x5c\xe5U\xea\xb3\xb4\x07\xc4\xd2f\ +@\xe8\xa0\xd0[\xf4\xc1+\x01=\xc9\xa8O\xe4\x5c\xda\ +GY\x9a\xbe\xd3\xc6\xf9\xf3#\x0f\x8e\xe7\x9c\xcc\x07`\ +\xa8\x97\xa9\xcf:\xe7\x87t\xf4\xbc`\xa4\xfaz\xfe#\ +\xd4o\x1d,z\xee\x83.\x93\xd7\xc6\xba\xef\x0a\x9d\xd9\ +\xdd\xf8\xcb\xa9^\xd72\xbd\x8f\xb2\x02h5\x82e\xcb\ +\x08\xafW\xc6\x85j\x84\x0b\x83Z\xbf\x5cB\x97\xb9$\ +\xe7v\x8a]\xcf\x02\xf8\xa0\xeb~\xf5\xbd$\x17vc\ +\x9fS\xc0\x87\xd8\xd1K.\xda\x02\xdeW\x05d\x88Q\ +\x9dO\xda\xd2\xdc\x87\x88\x040\xbc\xc3E\xec\xf7\x1c\x0f\ + \xdbl?}\x97\xd3\x1a\x10\x01\xe5\x92\xdd\xe6\x96\x83\ +x\xbe\x06G*\xa3\xd9\xb5\xe3\x8e\xa49C2\xad_\ +\x01B\x08\x87\xf8\xdc\xfaL\x80\x17\x14\x89\x1bo.\xa0\ +\xac4g\x08\x19s\xb3\xe9.\x93\xd7\xf7\xca\x806\xfb\ +,mv\xf8\xe9\x93\xa9kk\xa8\x99A\xaa\xc3\xf3D\ +JY_\x98}\x9a=,\xed\x01\xf1Y\xd6\xad.6\ +\xad\xd5\xfb[\xd0-.\xa3\xb9%\xc3\x10\xcaD\x17\xf1\ +Y\xaf\xf5\xd96O\x9a\xf1\xd7y\x99(\xcb\x11\xe3\xd1\ +ij\xe7\x90\xb7a\xb68\x83\x06\xc7\x8d\xd5e\x0ao\ +\xcd\x00\x224n\xde\x0e\x0c\xca;\x04 \xe6]T\x87\ +S{\xc7\xae>o\x1e\x8a\x1e\x9f\xf6\x80\x08\xcd\x15\xdd\ +\xbd\xe3*\xae@\xe4\x80\x87q\xf6>\x90]\x87\xd2\x12\ +R\x04K\xd3\x1e\x10\x12\xe5\x85\xc7C\xd9.\x80\xed\xf0\ +L\x97\xa6qgIY\xf3\xa0\xd1\x00\x8a3&K$\ +?K0\xd5\x05\xb1\xbfz\xe4@lZx\xc7\x18_\ +\x89=K\x80\x9c\xbe\xa3-z`\x00\x02\xfa\x03\xf0\x1b\ +\xd7\x11\xa2\xf6\xa7\xbb\xa9e\xbf\x07\xd1\xdc\xbb\x07\x0a*\ +\xf7\xbb\xc8\xef\xe8\x9e\xcdb\xb9\x00~e\x80\x00\x02\x80\ +\xf8\x8eI\xd48eCT,\xeb\x85\x1b\x84#F\x91\ +\x8d&\xffQ&\xa1\x80\x80\xdd\x9e\xed\xd0B\xbf\x8b\xe9\ +m\x1b0\x80\x88\xe0\xbe\x92\xb3uF\xe7\x1e\x9e\xb4\xe2\ +5\x81\xbc\xd2wk\xc5\x17\xc2\xc5\xab\xde2\xdeTS\ +`.DJ\xba\xbfy\x0e3\x97dxn\xe00\x04\ +P\xbev\xbd\xd8\xed\x82\xecI\xa3\x96\x8b\xc8\x22\x11\xb9\ +\xd8\x0b\xea}\x0e\x8d\x9f7\x14U=g\xbc$\x14R\ +\xa0<\xdeC\xb3g<\xec\xaa\xc4\xb7\x0f\x80=P\x00\ +\x01\xc0\x87\xbf\xd2\xbc\xc6\xb8\x01a/+\xe2\x0d\xc5+\ +\xd7S\x10\xa4\xe23B\xd9!W7%H\xa7\xc4O\ +@i\x01e\x1b\xa1\x9f\xd4\xa2f\x86'Wmp\xd3\ +\x1a|\xd4?\x1f\x90\xb9=\xb3\xac,7\xb2\xb8x\xd9\ +\x1e\x00\x95\xc0\x97\xcb=\x22\x22\x00\xa2\x22\xb8\xe8eN\ +u}L\x09=c(\xfcC\xb8\xb8\xf2\xfb\xbd\xb0u\ +\xaa\xe4t].cz\x88\x95\x95\xa3y>v\xa9~\ +Z\xb4\x0d\xae\x1b\xef:\xf9\x8e\xc8\xea|K\x05v\x8b\ +`R\xcfL\x1f:Z\xb3\xe3#\xcf\xb8\x1cB\x9ds\ +rQN\x8b3b\x8c\xdf\x1ed\xfb%\xda\x11\x8d\xc5\ +\xda\x01\xc4\x0en\x81]\xbe\xc0\xbf\x0b\xc07\xbc\xab<\ +$\xe1\xd0\xa7\x08\x167\x14W\xadK4\xd5g\x9c\x09\ +\x0d\x8av\xf8\xdf\x050\xa7w&B\xcf;P\xb4\xea\ +\x8f}\xd1\x19l\xaa\xddJ\xc8\xb7\xbc\x04$\xf1\xc7\xa1\ +\x89\x9a\xe9\xc7\xea\x16&RGis\xcd\xd8h\xbbo\ +ko\xc1\x00\x00\x07j\xfe\x0d\xa4*\x17\xbd\xbe\x97d\ +,\xe1\xe6hKo\x9c\x16\xa9]7\xbd\xf1\xa5\xd1^\ +w^r\xaav\xa4%\xb2\x1d\xe4\xbd}\x8b\xd0\xf1`\ +\xb0y\xcd7\xfb6{LI@\xaeE4Ok+\ +\xf6Q0R\xbdp\xc6\x99\x90gU\xd8\xc3\xe3W\xb4\ +@X\x7f\x03]\x04 \xd6\xe6\xe9\xc7\xeb\xee\xef\xf5/\ +\xb5\x8e\xa6\x96S7\xb7\x16@\xb6\x13|\x1fb}\xe4\ +\x14u\x1c9\xc8\x909b\x91y\x16\xb8\xc5\xb8\xe7\xb1\ +\xbc\xa9&\x0f\x90\x8f\x01\x8e\xe9{.\x8bvM\xbe\xae\ +\xb4\xf3\xfa\xf9\x9c\xd1\xbb\xcf\x8c\xab\xe8\xd2\xd1\xdf\xbd\xeb\ +n\xdf\xbf'\xdeWH\x89\xcf\x86\xa5*\xbc\xdc\xcd\x9f\ +\xbc79t?\x92\x8b\xa04\x0bp\x9a\x1am\xa2\xd0\ +\x01R(\xcc!0N(A\x08\xe6\x87\x8b\xaa\x8c\xb5\ +\xab\xe0\xf1\xea\xc7(\xdc\xe4\xd1\x88.\x028,\x82\x93\ +\x10}\x89\xca\x22\x94\xce\x15\x07\x93\x14Y(\xc0\xe0\xc4\ +LC\x7f\x01\xa4g\xcf\xf0\xbf\xe2g.\xdcs\xe4\xce\ +\x17\xbb\xb4\xdb3\xf6<\xe1\xef\xc8\xcd\xdfN\xf2\x1e\xa4\ +hKJ\x94\xe5\xe1pg\xfa\xf2\x86/0&\x9a\xb7\ +\xff6\xaeb\xbeE\x90\xe4g\xd7)\xe9\xd4=\xe1\x88\ +\x85\xba\x92\xe3\xa1B\x93\xbc\xbety=\xc8\xd5\x19@\ +\x92\xd7\x86\xfa$\xb0\xd6\x95G\xed\xb1_\x03\x08\xa7\xa2\ +\xb9\x22\xb8,\x05\xb7\x92\xca\xbci\xcd\xb5\x0f\x98\xa4\x0d\ +\xc1P\x8b\x80U\xa9\xf4\x12\x1a\x11\x11\xa5di\xb8\xa8\ +\xf2\xe5T\xdc\xdb\xabD\xcb\xcb3\xf6<\xd1\xe5Z\xc6\ +\xe4c\xb5\xa3D\xcb\x18\x08/\xa5\x06\x18\xd0\xb4\xac\xa5\ +\xf5\x85+_\x01\x12\xfc6\xa0DVKb\xb9\x05k\ +\x00,\xbf\xf6\xc5\xacS\xb5#\xdb\xa3\xfaY\x81\xfc \ +U\xdeU/\x22\x02\xc5%\xe1\xc2\x15\xebnvb\xe8\ +Ek\xd5\x96\xf5\xf5\xac\x98\x16[\xe11!*\x00\x19\ +\x9e:>C\x1c\x82\xcb\xc2EU\xbf\xe8\xfc}\xaa2\ +\x04\x00ri\xdb[c\x8a\xa3\xc9\xd4zC\xe9\xd55\ +\x15.\x0dwQ\x05Oe@@2\xe5\xb6\xf9\x08\xc4\ +!\xb9,\x5c\xd4\xf5\x92DJ\x03\x92r`\xb80#\ +U\xf3\x90\xd4\x05\x03\xe2(\xa8%\xdd-\xd6e\x18\x92\ +4f`I}q\xe5\xfa\x81\x96\xa9\xa7(3\xb0\xa4\ +\xa1x\xe5\xfa\x9e\x5c\xef\x03\xe4q\xe9\xc1\x9f\x83eZ\ +\x1f\x03\x0f\x9b-\xf5S\xcd{\xc8\xfe\xb7\xfd\x07:\xcc\ +\xccF\x8ay\xc7t\x00\x00\x00\x00IEND\xaeB\ +`\x82\ +\x00\x00\x02\xed\ +<\ +svg width=\x2248\x22 h\ +eight=\x2248\x22 viewB\ +ox=\x220 0 48 48\x22 f\ +ill=\x22none\x22 xmlns\ +=\x22http://www.w3.\ +org/2000/svg\x22>\x0a<\ +path d=\x22M24 40C1\ +9.5667 40 15.791\ +7 38.4417 12.675\ + 35.325C9.55833 \ +32.2083 8 28.433\ +3 8 24C8 19.5667\ + 9.55833 15.7917\ + 12.675 12.675C1\ +5.7917 9.55833 1\ +9.5667 8 24 8C26\ +.8333 8 29.3167 \ +8.575 31.45 9.72\ +5C33.5833 10.875\ + 35.4333 12.45 3\ +7 14.45V8H40V20.\ +7H27.3V17.7H35.7\ +C34.4333 15.7 32\ +.8167 14.0833 30\ +.85 12.85C28.883\ +3 11.6167 26.6 1\ +1 24 11C20.3667 \ +11 17.2917 12.25\ +83 14.775 14.775\ +C12.2583 17.2917\ + 11 20.3667 11 2\ +4C11 27.6333 12.\ +2583 30.7083 14.\ +775 33.225C17.29\ +17 35.7417 20.36\ +67 37 24 37C26.7\ +667 37 29.3 36.2\ +083 31.6 34.625C\ +33.9 33.0417 35.\ +5 30.95 36.4 28.\ +35H39.5C38.5333 \ +31.85 36.6167 34\ +.6667 33.75 36.8\ +C30.8833 38.9333\ + 27.6333 40 24 4\ +0Z\x22 fill=\x22#66708\ +5\x22/>\x0a\x0a\ +\x00\x00\x01\xb3\ +<\ +svg width=\x2248\x22 h\ +eight=\x2248\x22 viewB\ +ox=\x220 0 48 48\x22 f\ +ill=\x22none\x22 xmlns\ +=\x22http://www.w3.\ +org/2000/svg\x22>\x0a<\ +path d=\x22M13.05 4\ +2C12.225 42 11.5\ +187 41.7062 10.9\ +313 41.1188C10.3\ +438 40.5312 10.0\ +5 39.825 10.05 3\ +9V10.5H8V7.5H17.\ +4V6H30.6V7.5H40V\ +10.5H37.95V39C37\ +.95 39.8 37.65 4\ +0.5 37.05 41.1C3\ +6.45 41.7 35.75 \ +42 34.95 42H13.0\ +5ZM34.95 10.5H13\ +.05V39H34.95V10.\ +5ZM18.35 34.7H21\ +.35V14.75H18.35V\ +34.7ZM26.65 34.7\ +H29.65V14.75H26.\ +65V34.7Z\x22 fill=\x22\ +#667085\x22/>\x0a\x0a\ +\x00\x00\x01\xf7\ +<\ +svg width=\x2248\x22 h\ +eight=\x2248\x22 viewB\ +ox=\x220 0 48 48\x22 f\ +ill=\x22none\x22 xmlns\ +=\x22http://www.w3.\ +org/2000/svg\x22>\x0a<\ +path d=\x22M9 39H11\ +.2L33.35 16.85L3\ +1.15 14.65L9 36.\ +8V39ZM39.7 14.7L\ +33.3 8.29998L35.\ +4 6.19998C35.966\ +7 5.63331 36.666\ +7 5.34998 37.5 5\ +.34998C38.3333 5\ +.34998 39.0333 5\ +.63331 39.6 6.19\ +998L41.8 8.39998\ +C42.3667 8.96664\ + 42.65 9.66664 4\ +2.65 10.5C42.65 \ +11.3333 42.3667 \ +12.0333 41.8 12.\ +6L39.7 14.7ZM37.\ +6 16.8L12.4 42H6\ +V35.6L31.2 10.4L\ +37.6 16.8ZM32.25\ + 15.75L31.15 14.\ +65L33.35 16.85L3\ +2.25 15.75Z\x22 fil\ +l=\x22#667085\x22/>\x0a\x0a\x0a\ +\x00\x00\x1a\x93\ +\x89\ +PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ +\x00\x00J\x00\x00\x00H\x08\x06\x00\x00\x00Q\x18cz\ +\x00\x00\x01\x85iCCPICC prof\ +ile\x00\x00(\x91}\x91=H\xc3@\x18\x86\xdf\ +\xa6\x8a\x22-\x0ev\x10q\xc8P\x1d\xa4\x05Q\x11G\ +\xadB\x11*\x84Z\xa1U\x07\x93K\xff\xa0IC\x92\ +\xe2\xe2(\xb8\x16\x1c\xfcY\xac:\xb88\xeb\xea\xe0*\ +\x08\x82? \xae.N\x8a.R\xe2wI\xa1E\x8c\ +w\x1c\xf7\xf0\xde\xf7\xbe\xdc}\x07\x08\x8d\x0a\xd3\xac\xae\ +q@\xd3m3\x9dL\x88\xd9\xdc\xaa\xd8\xf3\x8a\x10\xc2\ +4\xc7\x10\x93\x99e\xccIR\x0a\xbe\xe3\xeb\x1e\x01\xbe\ +\xdf\xc5y\x96\x7f\xdd\x9f#\xac\xe6-\x06\x04D\xe2Y\ +f\x986\xf1\x06\xf1\xf4\xa6mp\xde'\x8e\xb0\x92\xac\ +\x12\x9f\x13\xc7L\xba \xf1#\xd7\x15\x8f\xdf8\x17]\ +\x16xf\xc4\xcc\xa4\xe7\x89#\xc4b\xb1\x83\x95\x0ef\ +%S#\x9e\x22\x8e\xaa\x9aN\xf9B\xd6c\x95\xf3\x16\ +g\xadRc\xad{\xf2\x17\x86\xf2\xfa\xca2\xd7i\x0d\ +#\x89E,A\x82\x08\x055\x94Q\x81\x8d8\xed:\ +)\x16\xd2t\x9e\xf0\xf1\x0f\xb9~\x89\x5c\x0a\xb9\xca`\ +\xe4X@\x15\x1ad\xd7\x0f\xfe\x07\xbf{k\x15&'\ +\xbc\xa4P\x02\xe8~q\x9c\x8f\x11\xa0g\x17h\xd6\x1d\ +\xe7\xfb\xd8q\x9a'@\xf0\x19\xb8\xd2\xdb\xfej\x03\x98\ +\xf9$\xbd\xde\xd6\xa2G@\xff6pq\xdd\xd6\x94=\ +\xe0r\x07\x18|2dSv\xa5 -\xa1P\x00\xde\ +\xcf\xe8\x9br\xc0\xc0-\xd0\xb7\xe6\xf5\xadu\x8e\xd3\x07\ + C\xbdJ\xdd\x00\x07\x87\xc0h\x91\xb2\xd7}\xde\xdd\ +\xdb\xd9\xb7\x7fkZ\xfd\xfb\x01\xa7vr\xbc\xf6x\x14\ +.\x00\x00\x00\x06bKGD\x00\xff\x00\xff\x00\xff\xa0\ +\xbd\xa7\x93\x00\x00\x00\x09pHYs\x00\x00.#\x00\ +\x00.#\x01x\xa5?v\x00\x00\x00\x07tIME\ +\x07\xe7\x0c\x0d\x0d+\x19\xea\xccr\xbc\x00\x00\x00\x19t\ +EXtComment\x00Creat\ +ed with GIMPW\x81\x0e\x17\ +\x00\x00\x18jIDATx\xda\xed[y`T\xd5\ +\xd5\xff\xdd\xfb\xde\xac\x99}\xb2/da\x93E@P\ +\xb1\xdaZZ\xfd\x94-\xc8\xe2\x04\x14\xb7JK\xe5\xf3\ +\x0b&\x80\x88\xd5\xb6Q\x8b~\x22\x90\x80b-\xa2b\ +QI\x18\x11H \x91\x8a\x15k\xddQPY\x0ck\ +\x12\xb2'\x93\x99\xc9\xccd\x96\xf7\xde\xbd\xdf\x1f\x81@\ +\x08\x89Qh\xc1\xaf\x9c?g\xee\xbcw\xde\xef\x9d\xfb\ +;\xe7\xfc\xce\x1d\xe0\x92]\xb2Kv\xc9.Y\xaf\xcd\ +\x91\xb7O\xedpl\x10.6\xbf\xc8\xc5\xe2\xc8\xf4\xe7\ +\xf8H@~\x82s\x0c\x03\xe1!\x0a\xfa1\x04\xe9\x0f\ +\x85st\x15\x97\x80\x02\xe0X\xe2\x8f\xa7:\xed\xef\x08\ +\xf8o8\xa0=\xe3\xebVp\xaca\xb2\xb8\xd89\x8f\ +\xb4\xfcG\x02\xe5\xd8\xc0\x05\xda \xfd\x0f\x07\xfd=\x01\ +\xb7\xf7\xec%\xa9\x00g\x8f\x17e\xab_\xf9\x8f\x02j\ +\xc6Jy\x02'\xfc\x7f\x01\x0c\xfd>\xbf\xe3\xe0\xdf\x10\ +\x8aYE\xf7\xab?\xff\x7f\x0d\xd4\xa4\xa7\x03\x89:\xbd\ +\xfae\x007\x9f\xcbu8\xe7\xaf\x8bT\xf5\xc8\x1b\xff\ +C*\xff_\x015\xe9in\xd4F\xc9\x8f\x10\x86\x5c\ +\x10\xa8\xbb[\xa7\x84$\xb8\x0f\xd6B\xd0\xa9aJ\x8d\ +\x86\xa0V\xf5t\xd9V\x0e\xb6\xbc\xd1\xa5^\xbc3\x8f\ +\xc8?z\xa0\xb2V\x86o#Dx\x0e\xe0\xb6n#\ +Da\xf0V6\xc1_\xed\x02g\x1c\x00@U\x14\xa6\ +\x94\x18\x18R\xec \x84\xf4\xc8_DQ\xe6\x15>\xa0\ +\xd9\xf4\xa3\x04\xea\xd6\x95\xd2\xcfD\x82\xa5\x1c\xb8\xba\x87\ +-\x84`S+\xdc\x07\xeb\xc0d\xe5\xackD\xad\x1a\ +\x96~q\xd0E\x9b\xbekC~\xaa\x12Tw\xbf\xf6\ +\xdf\xa4\xfcG\x01\xd4\xac5\xdc\xe6o\x93\x0a@\xc8\x9d\ +=\xad\x0b{\xdb\xe0>T\x0f\xc9\x1f\xec\xd5u5\x96\ +(\xd8.K\x84\xa8U\x7f\x17\xe1\xbf\xc0\x15\xd5S\xce\ +\x1cRuQ\x025s%7)T\x9e\xc3\x81\x05\xe0\ +\x88\xee\x96\x87\x22\x12Z+\x9b\xe1\xaf\xf9ae\x911\ +\xd9\x0eC\xb2\x1d\xa2\xb6g\xfe\x22\x9c,PY\x847\ +\xd6\xddE\x02\x17\x0dP\x8e\x15\xd2X\x22`\x09\xe1\xb8\ +\xbc\xdb7-3\xf8j\x5ch\xadl\xea\xe0\xa1\x1fj\ +\x82F\x05sZ,\xa2\xe2-=?\x01A9!\xe4\ +\xd1\xc2\xfb\xc57/8P\xb3\xd7*s\xbd>\xb6\xa2\ +\xa75A\x97\x0f\xde\xa3\x8d\x90\x02\xa1\xf3\xba\xcdU\x06\ +-,\x19q\xd0\xda\x0c=p=`\x8c\xa2\x8b^\xfc\ +\x95\xf0\xf4\xb9\xdc\x8b\x9e\xab\xb3\xdc\xef\xbf\x22%^\x80\ +^wv\xcc\x99\xac\xa0\xad\xa9\xf5\xbc\x83\x04\x00\x92?\ +\x04_\x8d\x0b\xbc\x9bD\xa0\xd3\x12$\xc4P\xc0\xe7\x1b\ +p\xae\xf7\x12\xcf\xf5\x02\xfb\xfe~\x14q\xe96)\xed\ +\x8ax\xc1\xa0\x17\xa9\xdb\xc3 \x9d\xe67\x15\x05\xd8/\ +K\x82!\xc1\x0a\xcf\x91zDZ\x83\xe7'\x9a\xa2\xb4\ +\xb0\xf4\x8b\x87\xd6\x1a\xd5\xf5\xa1\x04\xc0f\xa6\xa0R\x84\ +\x1d\xfe\xb0\x96\xb9\xaa[\xc9\x05\x07\x0a h\xa8p\xab\ +\x9a\xab[\xa5\xb8t\x0bRG$ (\x11\xea\xf2\xb0\ +\xceY\xcb\xacG\xdc\xc8\x0c\x04\xea=\xf0\x1ci\x00\x93\ +~X\x8dHE\x0aC\xb2\x1d\xa6\x94h\x10\x81v\xe1\ +\x11\xb3\x91\xc2\xa8\xe7J\xcd\xfe&\xd4|\xdbDeI\ +\x11A\x08\xbf\xe0@q\xc2\x09\x01\x81\x22+\xaa\xdaC\ +.\xb8j[\x91:4NJ\xedkS\xb5x\x19|\ +\x81\xce>F\xc5[\xa0\xb5F\xc1_\xebFke\xd3\ +\xf7\xcbxIv\x18S\xa3!\xa8\xbb\xba\x1d\xa5#\xb0\ +[(\x9a\x8e\xb5(\x07\xf6\xd4\x139$\xd3\x19\xd7\xd7\ +`o\xa5\x11{+M\xb8\x08\x22\xea\x8c\xfa( \xe1\ +\xe0\xa7\xd5\xaa\xe3\x07\x9a\xe4!?M\x859^+6\ +63Dd\xde9k\xa5\xc7\x22*\xc1\x0a\xf7\xc1:\ +\x84Z|=^Sm\xd2\xc1\xda/\x01j\x93\xae\xeb\ +\x03\x88@\x9cM\x80\x1c\x0c\xc9{\xdf\xad\x15\xbc\x8d~\ +\xe1Z\xa1\x12aQ\xc4\xc8\x0c/v~c\x07\x01\xf8\ +E\x00\x14\xc5\xd9\xfc\x08\xb6\x86\xc5]\xa5\x07\x11\x93j\ +\x8e\xf4\x1b\x95\xa8\x0eC\x84\xdb\xcbp:\xef\x8aZ\x15\ +b\x86\xf5A\xd8\xdb\x86\x96\xf2Z\xc8m\xe1\xceWV\ +\x09\xb0\x0dL\x82\xd6n\xe8\xd2\xc6\x10\x02D[)t\ +*\x8e\xf2\x8f\xab\xe4\xe6*\x8fh\x89mC\xd2\xc00\ +\xa6T\xee\xc7\xb2\xc8O\xb1pmR\xfbZ\x8e\x8b/\ +\xa2\xce\xb4\xa6J\xaf\xba\xb9\xaa\x15\xa9\xc3b\x918(\ +\x16\xfe \xe0\xf6v\xe5\xaf\x84\xab\xfa\xc1_\xe3\x82\xb7\ +\xaa\x19\x5cV`H\xb1\xc3\x9c\x1a\x03Bi\xd7t\xaf\ +\xa7\xb0Y\x08\xea\xca]\xf8\xfa\xabz\xc8\x92\x22\x02\xc0\ +\xe8[\x8e\xe2\xc0G\x09x04\xaes\xe6\x05\xbb\xf8\ +\x81:\xd9\xd3U|\xd5\x80\xfa\xc3nd\x8cL@j\ +\x8a\x19\xcdn\x86@\x90wbbC\xb2\x1d\xfa8\x0b\ +\x00\x0e\xaa\xea\xea\x9aVC\x10k\xa3\x084\x07\xf0\xd9\ +\xa6*\x84\xdb\xa4N\xdf\xbf\xf3\xca`0\x99\x9e\xa5\x96\ +\x12\xd8\x85\x07J\x11\x9f\xe7\x824\x86\x00\xa9\xdf\xb54\ +\x14\x88`\xff\x07\x95\x88\xb2j1\xf8\xfa4XLj\ +4\xba\x14\x9c\x9e\x00\xa9J8k\xba\xb7[(\x88,\ +\xe1\xdb\x0f\xea\xe0\xaa\xf6vS\xb3\x9d\xb5,t\x83\x93\ +\xd7.\x8a\x16\xe6g\xd9\x87b\xd4\x11\xfaN\xc8\x13\x18\ +\xce\xe5\xde\xbf\xbc\xf8~v\xa4\x0f\x8bE\x88\x8b\xf0\xb6\ +v\xae\xbf\x00\x80\x12\xc0j\xa6\xd0\xab9\xaa\xbei@\ +Mys\xaf\xdb\x1f\x22P\xe8l\x86]A\x8f\xe7\x96\ +\x0f\x0bG\xd6^P\xa0&M\xcd\xb9\x8ed\xdc`\xd3\ +\xa5\x8e]\xc6\x81\xferH\x82\xf7h\x03\xda\x1a\xbd\xbd\ +w\x80\x12\xf4\xbd2\x11q\x196x\xfd\x1c\xa1\x08\xc0\ +\x19\xa0\xd5\x00V\x13Ec\x85\x1b\xc7\xf6\xd4A\x0a\xf6\ +\xbe\xee\xd2Z\xa3`\x1d\x98t\xa2q\xe6\xfb\xa5\xa3\xa5\ +\x8fJ\xc7w\x86J\xde,(\xfb\xc1\xfd\xe5\xb9\x005\ +h\xf4X_\xca\x15\xe3WP\x8dqTDj\xaf\xc2\ +\xf51&hLzH\xbe\x10\x98\xa4\xf4\x82\xc0\x80\x96\ +\x1a\x1f\x9a+\xbd0[T\xb0YE\x18t\x80\xec\x0f\ +\xe2\xdb\x0f\xabPw\xd0\x05v\x22J\x09%P\xa9E\ +0\x85u_F\xf4O\x809#\x0eTl\x7f4\x83\ +\x9e\xc6\x98-\xa6\x18_\xf9\xae%\xe5\xe5\x1fF.\xd8\ +\xd6\xbb\xf9\xd7\xdf\xbe\x9c:P\x04\x15\x5c\xee\ +\xce\xfd^\xa7lC\xdb\xfb<\x15\x14\x1c\xdb]\x87\x86\ +c\xee\xefQ\x93\x85\xa1Ay\xb9\x06_\xe6\x96\xbdY\ +P\x06p\x02\x10\x9e9}\xc1-\xe0\xca\x03\x9c\xa0B\ +\x11\xcd9e\xaf\xe7\xb5\x02\x80\xc3\x91\x1d\xd3F\xc4\xf9\ +\x04\x90h\xc8\xbb\xe4\xf4\xc8#\xdd\x91\xb5\xc00\x8fp\ +ZQw\xd4\xff\xec\xe9)t\xf2\xe4\x1c\x8b\xac\xa1\x0f\ +\x83\xf3\x0cF\xe8\xf6\xd2\xc2\xa5/e=\x17\x99\xc9\x82\ +\x81\xb5\xad\xb5~\xc1_\xe3\x06\xbe\xc78*iP4\ +\xfa\x0c\x8aAP\x11\xe1\xf6\x9d\xe2/B\x00\x8b\x91\xc2\ +\xa8\xe5\xa8\xda\xd7\x88\x9a\xf2\xe6^e\xca\x93\x15|T\ +\x82\x0d\x86D\xbdG\xd4\xeb\xe7\x1fy\xf5\x81uI\xe9\ +Q\xd3\x18\xe5\xb7\x81\xd2}\xac5\xb4\xb2\xb4\xf4T\xb2\ +\x1a\x959[\x9f\xa0\x8f\x9a\x0b\xf0\xc1r\x18\x7fx\xfb\ +D\x80\xf4\xba2\x9f8c\xee\x0dP\xc4\x1c\x85\xf0\xd5\ +\xa3\x07\x9b\xb7}\xbe\xdf{'%\xb8\x81\x81?\xb5\xad\ +\xa8\xe0@fV\xcem\xa6A\x93\x17(1\xd7\x8d\xe4\ +'\xb0\x91C\x11\xb8\x0f\xd5!\xe4\xf2\xf7^\xc2P\x0b\ +\xe8;*\x11\xf1}m\x08\x8698\x07t\x1a\xc0]\ +\xef\xc7\xc1\x8f\xab\x11n\xeb}\xe7\xa1\xb1\xe8a\x1b\x90\ +\x08Q\xaf\xe9h\xac\xa9\xeb\xa3\xc3\xad\x07\x8a\x1f/)\ +\x5c\xb6n\xe2\xb4\x07\xfbC\x94\xf38\xc7\xdf\xb6m\xc8\ +\x7fuR\xd6\xbc_0\xce\xfe\x9b\x13a\xcd\xb6\x0d\xcb\ +\xb6\xff\xe0\x16&//\x8f\xee\xda\xef\x99\x03\x90\xe1\xe0\ +\xec\xdd\xad\xce\x15E\x9d\xe6z\xcfz^\xb6$\x9a~\ +\xe5\xf20\xf8\xdbNER[c+\xbcG\x1b \x87\ +z\xff\x90z\x93\x16\xd6D#\x08\x01\xbcM\x01\xf8\x9a\ +\xdb\xce\xd8\xaez\x08j\x01-5]\x8bV\x95N\x03\ +Sz\x0c\xf4\xb1\xe6S\xd7\xd3\x12\xd8\xcc\x94\xb5\xd6\xb5\ +\xbe\xf2\xe2\x03\x96_\x9f\xbe~|Vn\x16!\xb8\x9d\ +3|X\xea\xcc\x7f\xe6_\xde\xeb];u\xf7+\xe6\ +X\xc3\xed\xfd\xaeNV\x04\xbdF\xe7\xf2(\x88H'\ +\xeb%\x0e\x7fm\x0b|\xc7\x9b\xa1\x84\x7f\xf8\xc9\x9c(\ +\x8b\x16\x1a\xbd\x0a\xb6$#B\xfeH\xa7^\x8f\x88\x14\ +\xa6d;\x8c\xa91\x1dm\x92Z\x0d\xd8\xcd\x14\x92\xaf\ +\x8d\x1f\xdeU\xc7}M\x81W?z\xeb\x8a{/\xf8\ +\xb8\xca\xdb\x14P\x7fQz\x90\xc5gXy\xea\xb0X\ +\x22\x11\x15<^\x06\x09\x04\xc6d;t\xd1&\xb4V\ +4\xa2\xad\xc1\x0b\xce{\xcf_*\xad\x08\xbdI\x03[\ +\x92\x11L\xe1\xa7\x9a\xe6\x93\x11\x13k\x86)=\x16*\ +\x9d\xba\x83\xd7lf\x0a\xad\xa0\xb0\xea\xbd\xf5\xbc\xf6p\ +\x0ba\x92B/\xaaq\x158\xa7\xf5GZ\xe0\xae\xf5\ +)\x89\x03\xa3\x95\xf8\x016Uk\x1b!\xbe\x00\x87\xa8\ +U\xc1vY\x12\x0c\xc96x\x0e7 \xec\xe9\xdd\xb9\ +\x89\xc4~6\x08j\x01\x9cs\x10\x00\x8d\x15\x1e\xb4y\ +\xc3P\x1bu\xb0\xf4\x8d\x87\xc6r\xaa\xcf3\x1b\x08\xac\ +f\x8a\xe3{\x1bP}\xa0\x99\xca\x11\x05\xf7\xdcp\x1c\ +\xbb\x8e\x98\xb1\xb7\xe2b\x1cW\x05%\xe1\xd8\x9e:\xa1\ +\xee\xb0\x0b}\xafLTR\x12LB\xb3\x87\xa1-\xc8\ +\xa16\xe8\x10;\x22\x0dm\xf5\x1ex*\x1a\xbfS~\ +\xe1'\xc8\xc1u\xdc\x87\x90/\x0cY\xe1\xed\x873\x12\ +\xac \x94\x9c\xd0\xd1\x81h\x8b\x80\xa0\xbbM\xda\xb3\xbd\ +\x96\xf8\x9a\xdb\xc4\x1b\x85ChS\xa9\xd17>\x88\xd2\ +/b/\xeeqU\xc8\x1f\xc1\xbe\x9d\x15\x82%\xde\xc8\ +/\xbb&\x89\x98\xa2Tp\xb9\xdb\xe5^}\xbc\x05Z\ +\xbb\x11m\x0d^x\x8e\xd6w+\xef\x06\xbda\x04\x03\ +\x11\x04\xdc!\x18R\xec\x88I\x89\xee\xd0\xd4E\xa1}\ +\x5c%0\x19\xe5\x1fT\xa1\xa5\xd6\xa7\x8a\xe9\xe3\x83u\ +h\x04\xd7\x1d\xaa\xc4\x9f\xa5k\xf0\xfb\xd7\x0d?\x9eq\ +\x95\xa7\xdeG>\xd9\xfc-\x92\x07\xc5 mh\x0c\x02\ +\x92\x00\x97\x97\x81\xaa\x04\x18\x92m\xd0F\x1b\xe1=\xd6\ +\x80\xb6\x86\xae\xf2q\xd3q/\xb46\x03\xe2\xae\xea\x0b\ +\xd5\x89tO\x08`6PX\x8d@\xf5\xfe&T\xed\ +k\x84,\xb5\xd7W#n\xa8\xc2W\xef\xa5\xe0\xb1\xf0\ +\x8d?\xceq\x15\x00T\x1fhBs\x95\x07)\x83b\ +\xd0g@4\xbc\xbe\xf6~O\xd4\xaa`\x1f\x94\xdc\xae\ +W\x1d\xac\xeb8\xc4!\xea\xd5\xb0\xf6K\xe8t\xa4\xc7\ +\xa8oW-=5^|\xb2\xa3\x06\x913t\xf4\x1d\ +k\x07\xe3,\xda\x22\x08\xa8r\xee)\xeb\x1c\xed\xea)\ +{\x06\x08\x14\xdb\x09\x90\xf6}\xfa\xbdacR\xa16\ +\xe9P\xdb\xd8\xb9}\xe1\x0a\x03g\xbc\xd3\xd8J%\x02\ +16\x01,\x10\xc4\xa1\xcfk\xe1i\xec\xfd!:\x0e\ +\xd4sQ\xf8\xe5'E\x97\x1f\xb8\xa0\x11uE\xe6p\ +\x11L6\xc7\x86\x9b\xb0\xe7\xcb\x16\xb8<\xdf]/\x85\ +\xfc\x11|\xb6\xf5\x10\xe2\xd2\xcc\xc8\x18\x99\x88\xc8\x89q\ +\xbb\xa4\xb4\x8f\x99\xc8\x09\x8c(m\xaf\x87\xf4*\x86\xa3\ +\xbb\xabQ{\xa8\xf7\xc7\x19\x0dz\x8a\xd1\xd7\xc6\xc2\xad\ +\xb1\xb5IP\x91O\x8a.\xf0\xd6k\x0bJ\xf3\xef\xbf\ +Y\xb4\x8e\xee\x9b\x00\x97'\x1a\xafnl\xc0\xc62W\ +\xaf~\xdbP\xe1EC\x85\x17\x19#\xe2\x918\xc0\x0e\ +\x7f\x84\xb6O\x8fy{6\xb3\x18\x08j\xcb\x9b\xf0\xf5\ +7M\x90\xa5\xde\xed\x1eB\x80\x9f\x8f6c\xd1\x9c\x14\ +\x18\xa3\x04\xbc\xb7\x9fe\xbc\xf4\xbe\x94\x0d`\xce\xbfe\ +\xb80e\xca\x82\xd8\xb3}\xae3\xa8\x1e[\xf5\x8e\xb2\ +\xf1/\x7fW\xc0\x04\x15\xe6\xcdJ\xc6k\xf9\x031\xa4\ +\x7fT\xaf\x9d8\xba\xa7\x1e\xbbJ\x0e\x22\xd4\xe4\x81U\ +-\xc1\x1e\xa5\x00~?v\x95\x94\xe3\xc8\x97\xf5\x1d \ +\x09\x22\x85F\xd7\xfd,`\xf8 =\xfe\xfcD?,\ +^\x90\x06\x85\x08X\xfb\x0f\x86Wv\xb2\xf7\x94\xb0\xea\ +\xac\xc7\x12\xc7M\xcf\xee{\xde8j\xd2\x1d\x0f\x0cT\ +$\xb2\x88pb\xe4\xe0\xc7\xb9,\x15\x94\xbe\xb5\xaa\xe3\ +\xaf\x15\x99\xd3\xe7]\xc3\x81\x1c\xebe\xe3+Y\xdc\xf5\ +YY\xa3\x85\xb4_\x0e\xa60h\x81\xb7\xdfwcM\ +Q=\xea\x1a#\xe7\x9c\x0cD\x95\x80\xb4\x11q]Z\ +\x18\x00\x88\xb6\x8a\xf8\xf5\x8cxd\xde`GH\x02J\ +\xf70\xbc\xb3W)\x8f4~\xb3\xb1\xf1\xcb\xd7b\x14\ +N\x0b\xca\x9c\xcb\xf6\x9f\x5c\xefp\xe4\xdaB\x94\xcfa\ +\x0c\xfd\x08A\x1d\xe1\xf2\x8b%\xce\xe7\x8e\xfd \xa0&\ +O\xce\xb1DT\xf4^B\x90f\x12\xb4\x8b\xd7\xaf\x7f\ +\xb2a\xe2\xed\x0f\xf6\xe7\xb24\x1f\xa0\x9f\xe99\xdf\x1e\ +$\xec>\x80\xc8V\x8d\xb8t\xdd\xba\xa5\x81)3\x1f\ +\x9c\xa8\xc9\xb8%;~\xe05\x03\xa6]I\xd2~:\ +\x90\xa2-\xc8\xb0nS\x03\xde\xda\xee\x82?\xf0\xfd\x93\ +\x0f\xa1\x04\x89\xfd\xed\x105\x02T\x1a\x01\xa1@\x04\xd5\ +\xfb\xdb\x81\xd2i)\x1c\xe3\xa3q\xf7\xb4xh5\x04\ +\xdf\x1c\xe7X\xff\x11\xf3\x1c\xfe\xe6\x93j\xb9b\xdb\xd2\ +M\xaf?\xf5\xea\xa4I\xf7\x1ae\xad\xe9a\x01\x84B\ +\x91\xd6BP\xdf\xc4\xc1\xae&\x8a\xf0\xa7\x92\x8d\xcb\xbe\ +\xbd\xe9\xce\xfbbUa\xdd\x22\x01\xc4\x0dAY]\xbc\ +~EC\xaf\x80\x1a\xecp\xa8\xd3\x912\x0b`#\x04\ +\x82\xbf\x16o(\xf8\x10\x00~\xe2\xc8\xd5Y\x18\xb9\xac\ +l\xe3\xf2\xdd\x99Y9\x93\x01:\x81P\xe9\xf9\xe2\xc2\ +gw\xdf<\xfd\xfe\x91\x22W\xcd\xa6\x1c\xc7I\xb8u\ +\xa5j\xf8\xaf\xfb\xa9\xd5\xdam\xa3\xaf\x1a\x16?q\x04\ +%}\xe3\x08\xea\x1a#xiC=\xde~\xbf]\xdf\ +\xee\xdd\xb8\xca\x04\x83U\xdb1\xaeR\x14\x0e_s\x00\ +\x9e\x86\x00~>\xda\x8c;\xa7\xc4bP?=\x8e4\ +rl\xfa\x9c){\xf6|\xed\xd5\xb1\xc6\xc9\x87\x8f~\ +\xf2\xb9\xa1\xadu\x16\xe7,\x81I\xaaW\xca6?s\ +d\xc2\xd4\xdc\x0c\x22\xe07\x10\xc8\x8e\xad\x85\xcb\xdf\xbd\ +yZ\xee\x90\xe6\x8a\xc0\xc1\x93Z\xdb\xcd\xb7\xdd?D\ +T\xd4\xb3\x00\xb2w[\xd1\xb2W\xce\xec\x0f;\x015\ +nF\xf6\x15\x22\x13~\xcb\x18\xd9\xa9'\xd5N\xa7\xd3\ +\xa9\x00@\xe6\xad\xf3\xc7q\xca&\x11\x10?\xe7\x5c\xab\ +\x92#y\x9b6\xadr\x8d\x9b\x99m\xa2\xb2\xb0\x10\x5c\ +p\xcb\xb2\xef\x8d\xedo\xad\xae\x9b\xe0\xc8\x99\x03JF\ +1\x905\x833\x97)M\xae\x06\xe7O\x86D\xa7\xce\ +\xbcN\x80Q\x0b\xec\xfa\xc6\x8f\xd5o\xd4c\xdf\xa1\xef\ +N\xf1\xa9Cc!\xa8\x050\x99\xc1\xef\x0e\xa1\xf9\xb8\ +\x17}Su\x98{w\x02\xae\x1cfDD\x02\xde\xda\ +\xc5\xb0\xe3\xcb\x96\xe3\x01n\xb9\xab\xed\x1f\x0b\xea\x18!\ +\xd9\x9c\x93\xdd\xdb\x9c\xf9/\x8d\x9d\x91\x93\xa6b\xe4>\ +\x80\x8a\x11\xce\x9e\xdc\xee\xcco\xc9\xbcm~4g\xca\ +\xc3\x04\x82\x9f16\x94\x10\xf2\xd2\xd6\x0d\xcbKO\xd3\ +\xcf\xef\xa6\x84\x8f!\x84\xac*.\xcc\xdf\xd5\x09\xa8\x89\ +\x8e\xb9}\x00q\x11'Ju B\x9e\xdf\xb9\xb9\xc0\ +\x03\x00\x99\xb7\xe5\x0e\xe1\x0a\xf2@\xf0\x191\x07V\x95\ +\xac^\xdd6ujnFD\x85\xdf\x00\xd0\x81\xa3\x0f\ +\xa7d\xe9\xb6\xc2\xe5\x1fMt\xe4\xfc\x84\x80\xdc\xa3\x00\ +\x9bJ\x9d\xf9o\x8fsd\x0f\x16\x88\xf0\x18\x08\xf9\x22\ +i\xdc3\x95Q\x22\x16\xffb0I\x1f;\x9c\x82\x12\ +\xa0\xec\xbd\x16\xac\xd9P\x8f\xc6f\xa9\xc7q\x95,)\ +\xa8;\xd4\x02\xabI\xc0\xad\xe3\xa2\xe1\x98\x10\x03\xbd\x96\ +\xa2\xec+\x86\xed_\xf3\xa3m\x11\xb6\xb8n\xc7\xc3j\ +p>L&l\xc9\xdb\x85\x05\x15\x93\xa6\xcfs0\xce\ +o\x13\x80\xe7\xb7l\xc8\xdf1\xc9\xf1\xc0@\x85\x0a\x8b\ +\x08\xb8\x97p\xae\x85\x22\x14\x94l\x5c\xf6\xed\xec\xd9\xb3\ +U5n\xfd\xaf\x08\xc1U\x22\xc5\xe2\xcd\x85\xed\xca\xe6\ +\x09\x15\xf7^\x02>\x98\x04\xd9#\xc5\xc5+\x1a\xc8\xc9\ +Y\x9e \x10\xba\xa5p\xf9q\x00\xb8a\xca\xc3v\x9d\ +*2\x0b\xe0\x86\x88D\x9f\xfb\xdb\xa6\xa5\x8d]\xce\x1c\ +L\xbb\x7f\xc8\xf6\x8d\xab\xf6M\xbd}~jD\xe6\x0f\ +r\xa242\xd1Z`\x88\xec\x0b\x04y\x9f\xf9 J\ +\x9c\xa4`\xe9\xf6\xb7\x0a\xea&L\xcf\x19\xa4QG=\ +\xa2\x1f>\xab6.>%w\xc6\xb5T\xbc2\x9d\xc2\ +\xed\x95\xb1\xe5\x1d\x17^,\xac\xefV\xf9\x94%\x05Y\ +\xe3\xa3qoV<\x8cQ\x02\x0e\xd6q\xbc\xf6!\xe3\ +\x87k\xfck\x02\xbb\x96E\x0b\x01\xdf#\x9b6\x15\x1c\ +\x18;#'\x8d*\xc8\x11\x08\x91}\x8d\xbb\x17\x99L\ +W\xeb\x98F\xba\x97\x10\x0cP\xe4\xc8\x92\xd2\xb7VU\ +\x8ew\xe4\x8c(u\x16\xec9\xfb\x88\x8e\xce\xe1`j\ +I#>\xfa\xb7uK\x03'?\xf7\xd3\xb0\xf7\x9fo\ +\xfc\xd9\xdd\x85\xa3\xc6e\xe5\x5c'p\xba\x90P\xf2\xa7\ +\x92\xa2e\xdd\xfe\xd3r\xdc\xb8l\x8dh\x14\xe7\x03$\ +>\xc2y\xdevg~\xcb\xa4\xe993\x19\xe8\x14\x81\ +(\xcfl)\x5c\xf1\xe9\x98\xc99\x16\x83\x9a>\x03\xc2\ +\x9bi\xd0\xfbdq\xf1\xcb\xbe\x19\xf9\xc14\xb5V\x95\ +wy\x1f\xdc}\xe7u\x14q\xe6v\xfeZ\xfab\x0d\ +>\xd9\xdd\xda\xe9\x1e\x83\xfa\xea\x913+\x09C\x07\xe8\ +\xd1\x1a\x04^\xda\xa9\xe0\xcbc\xfc\xcd\x18\x8b\xf8P\xfe\ +LrtT\xe6l}\xbc\xce\x90\x0d\xf0>\x8c\xcby\ +e\xceg\x9bnq\xe4^\xce\x80s\xa6\x82\x93\ +\xe9\x84\xd3g\xb7:\x97\xfd\xb3}\xac5o\x1a\xe7\xfc\ +A\x05\xda\xcc2\xe7SM\xe3\xa7\xde\x9fJ\x05\xf5\xfb\ +\x80r\xc7V\xe7\xca\x7f\x9ey\x8d9/\xf1\x81~I\ +.\xbaa0\x1d>\xf5J\x0a\xa3\x0e\xd8s \x80\xaf\ +\xf7\x07\x00\x02\x0c\xe9\xaf\xc3\xa8\xcb\x8d\x88\xc8@\xc9n\ +\x86w\xf6\xf1c\x01?\xbf\xf7\xb5l\xd5\xce.\xfe\xdc\ +>\x7f\x14\x95\x95\x22\xceC?\xdf\xea|\xa1\xe6F\xc7\ +Cf-\x09\xbfK\x08\x7f\xb1\xa4h\xc5_\x00`\xca\ +\xf4\x05C%\xae\xf6\xcb\xb4\xa6\ +\x17\xef\xba9>p\xe6x\xcd\x10\xeb]\x00\xf0t\x95\ +\xa4[\xb8i\xd3\x93-\x99\x8e\xdc;\x002Y\xa0\xe2\ +\xc2\xcdE\xcf\x1c\xe9\x02jV\xce\xbd\x00\xb9\x99E\xc4\ +\xdf\x95m\xee\xfa\xfd9\x035qz\xce\xf3`\xdcK\ +\xc3\x9a'\x8b\x8b\x97t{$e\xf2\x8cEi2\x0f\ +\x95\x10\xd0\x87K\x8a\x96o\x05\x80\x89\xd3\xe6\xf6\x87 \ +\xfcU\xe0X\xb2\xc5\x99\xdf\xe5\x9f\x9a\xab\xde\xe3\x86\x0f\ +\xf6\xcb\xf3\x93\xcc\xe4\xd11\x83\x89\xa8\x12\x80\xcf\x8e2\ +^\xd9L\xfe\xaa\x88B\xce\xda_\x11\xcf\xd9&D\x9c\ +\x09\x8fPB\x1f:I\x11\x99\xd3\xe7M\x04\xf8\x1f\x05\ +\xc2\x1d'\x89\xf9l6*s\xb6>!*\xeaa\xae\ +\x10\xc2\xfc\xd2\x13ee\xcf\x86\xcf\x1bP'\x07\x9e\xdd\ +n\xc7\x09s\xac\xd4\xa0\xcf\x01W\xe2|\x11,:\x99\ +9o\xbf}\x8e\xb5U\xd1<\x0a\x0e\x9f\xe2\xcbx\xaa\ +\xacln\xb7N-\xde\xc2\xe3\x0e7*\xd7\x1850\ +E\x10\xdc\xfe\xc2]\xc6\xc6\xee\xd6\xdes\xcf=\xda\xe6\ +\xa0\xed1\xce\xb9A\xa4\xe4\x7fO&\xa2)S\xee\x8b\ +\x8d\xa8u\xbf'\x9c\xb8\xfc*q\xcd\xce\xd7\x97Tw\ +\xcf\xb3+5=\xf9s^e\x961c\xc6\x88\xa6\xd8\ +Q3\x15\xb0_\x10EY\xbc\xf5\xc48~\xd4\xa8\xd9\ +\xaa\x84\x8c\xa8\xdf\x82\xf0\xab\x08\x17\xf3J\x9cK\x8f\xfd\ ++t\xae\x89w>\xd4\x07\xe1\xf0\x13`d\xe7\xd67\ +\xf3\xd7\x9e\x94[\xc7\xde\xb1p\xa0\x18\x91\x1e\x22\x9c\xff\ +\xa3\xc4Y\xb0\xf6\x82\xeaQ\xe3fd_!0\xf1\x11\ +\xae\xf0\xd2m\x1b\x0b^\xee\xe8\xffn\xcd\xbd\x89S>\ +\x93\x02\xabOV\xf6\xffj\x9bp\xeb\xdck\x09\x11\x1e\ +\xa6`/\x16;W\x14\x9fJ<\x0f8\x08\xa7\xf7@\ +\x22\xbf\xdb\xbai\xf9W\x17\x06\xa8\x99\x0b\x93=\x11\xc9\ +\xf5\xb13?\x08\x00\xe3\x1c\xd9\x83E*\xce\x03#\xc7\ +J\x9c\xcb\x17\xe3\xdfl\xb3g\xcfV\xd5y\xa3~\xc3\ +\x19\x06\x10H+N6\xba?q\xe4\xeabDf\xea\ +\xae\x8f\xfb\xb7l\xbd\x8e\x1a\xc4\x91;V!d\x8c\x10\ +R\xf2\x8b\x8b\x7f\xb8C\xe7\xc3\xc69\xb2c\x04\xaaZ\ +\xc4\x99\xf2\xfe\xb6\xd3\xa2\xeb\xe2\xb0\xb3\x89\xd5\x17\xd8\xf2\ +\xf2\xf2(.\xd9%\xbbd\x97\xec?\xc0\xfe\x0f\x14\xd5\ +\xea\x92\xe4\xa3Oi\x00\x00\x00\x00IEND\xaeB\ +`\x82\ +\x00\x00\x012\ +<\ +svg width=\x2248\x22 h\ +eight=\x2248\x22 viewB\ +ox=\x220 0 48 48\x22 f\ +ill=\x22none\x22 xmlns\ +=\x22http://www.w3.\ +org/2000/svg\x22>\x0a<\ +path d=\x22M12.4501\ + 37.65L10.3501 3\ +5.55L21.9001 24L\ +10.3501 12.45L12\ +.4501 10.35L24.0\ +001 21.9L35.5501\ + 10.35L37.6501 1\ +2.45L26.1001 24L\ +37.6501 35.55L35\ +.5501 37.65L24.0\ +001 26.1L12.4501\ + 37.65Z\x22 fill=\x22#\ +667085\x22/>\x0a\ +\x0a\ +\x00\x00\x04\x83\ +<\ +svg width=\x2248\x22 h\ +eight=\x2248\x22 viewB\ +ox=\x220 0 48 48\x22 f\ +ill=\x22none\x22 xmlns\ +=\x22http://www.w3.\ +org/2000/svg\x22>\x0a<\ +path d=\x22M10.3929\ + 26.4C9.73097 26\ +.4 9.16667 26.16\ +43 8.7 25.6929C8\ +.23333 25.2215 8\ + 24.6548 8 23.99\ +29C8 23.3309 8.2\ +357 22.7666 8.70\ +71 22.3C9.17847 \ +21.8333 9.74513 \ +21.6 10.4071 21.\ +6C11.069 21.6 11\ +.6333 21.8357 12\ +.1 22.3071C12.56\ +67 22.7784 12.8 \ +23.3451 12.8 24.\ +0071C12.8 24.669\ + 12.5643 25.2333\ + 12.0929 25.7C11\ +.6215 26.1666 11\ +.0549 26.4 10.39\ +29 26.4ZM23.9929\ + 26.4C23.331 26.\ +4 22.7667 26.164\ +3 22.3 25.6929C2\ +1.8333 25.2215 2\ +1.6 24.6548 21.6\ + 23.9929C21.6 23\ +.3309 21.8357 22\ +.7666 22.3071 22\ +.3C22.7785 21.83\ +33 23.3451 21.6 \ +24.0071 21.6C24.\ +669 21.6 25.2333\ + 21.8357 25.7 22\ +.3071C26.1667 22\ +.7784 26.4 23.34\ +51 26.4 24.0071C\ +26.4 24.669 26.1\ +643 25.2333 25.6\ +929 25.7C25.2215\ + 26.1666 24.6549\ + 26.4 23.9929 26\ +.4ZM37.5929 26.4\ +C36.931 26.4 36.\ +3667 26.1643 35.\ +9 25.6929C35.433\ +3 25.2215 35.2 2\ +4.6548 35.2 23.9\ +929C35.2 23.3309\ + 35.4357 22.7666\ + 35.9071 22.3C36\ +.3785 21.8333 36\ +.9451 21.6 37.60\ +71 21.6C38.269 2\ +1.6 38.8333 21.8\ +357 39.3 22.3071\ +C39.7667 22.7784\ + 40 23.3451 40 2\ +4.0071C40 24.669\ + 39.7643 25.2333\ + 39.2929 25.7C38\ +.8215 26.1666 38\ +.2549 26.4 37.59\ +29 26.4Z\x22 fill=\x22\ +#667085\x22/>\x0a\x0a\ +\x00\x00\x037\ +<\ +svg width=\x2224\x22 h\ +eight=\x2224\x22 viewB\ +ox=\x220 0 24 24\x22 f\ +ill=\x22none\x22 xmlns\ +=\x22http://www.w3.\ +org/2000/svg\x22>\x0a<\ +path d=\x22M0 12C0 \ +5.37258 5.37258 \ +0 12 0C18.6274 0\ + 24 5.37258 24 1\ +2C24 18.6274 18.\ +6274 24 12 24C5.\ +37258 24 0 18.62\ +74 0 12Z\x22 fill=\x22\ +#E6E6E6\x22/>\x0a\x0a\x0a\x0a\ +\x00\x00\x00\xdf\ +<\ +svg width=\x2248\x22 h\ +eight=\x2248\x22 viewB\ +ox=\x220 0 48 48\x22 f\ +ill=\x22none\x22 xmlns\ +=\x22http://www.w3.\ +org/2000/svg\x22>\x0a<\ +path d=\x22M18.9002\ + 35.7L7.7002 24.\ +5L9.8502 22.35L1\ +8.9002 31.4L38.1\ +002 12.2L40.2502\ + 14.35L18.9002 3\ +5.7Z\x22 fill=\x22#667\ +085\x22/>\x0a\x0a\ +\x00\x00\x01V\ +<\ +svg width=\x2248\x22 h\ +eight=\x2248\x22 viewB\ +ox=\x220 0 48 48\x22 f\ +ill=\x22none\x22 xmlns\ +=\x22http://www.w3.\ +org/2000/svg\x22>\x0a<\ +path d=\x22M9 42C8.\ +2 42 7.5 41.7 6.\ +9 41.1C6.3 40.5 \ +6 39.8 6 39V9C6 \ +8.2 6.3 7.5 6.9 \ +6.9C7.5 6.3 8.2 \ +6 9 6H23.55V9H9V\ +39H23.55V42H9ZM3\ +3.3 32.75L31.15 \ +30.6L36.25 25.5H\ +18.75V22.5H36.15\ +L31.05 17.4L33.2\ + 15.25L42 24.05L\ +33.3 32.75Z\x22 fil\ +l=\x22#667085\x22/>\x0a\x0a\ +\x00\x00\x01f\ +<\ +svg width=\x2248\x22 h\ +eight=\x2248\x22 viewB\ +ox=\x220 0 48 48\x22 f\ +ill=\x22none\x22 xmlns\ +=\x22http://www.w3.\ +org/2000/svg\x22>\x0a<\ +path d=\x22M24.45 4\ +2V39H39V9H24.45V\ +6H39C39.8 6 40.5\ + 6.3 41.1 6.9C41\ +.7 7.5 42 8.2 42\ + 9V39C42 39.8 41\ +.7 40.5 41.1 41.\ +1C40.5 41.7 39.8\ + 42 39 42H24.45Z\ +M20.55 32.75L18.\ +4 30.6L23.5 25.5\ +H6V22.5H23.4L18.\ +3 17.4L20.45 15.\ +25L29.25 24.05L2\ +0.55 32.75Z\x22 fil\ +l=\x22#667085\x22/>\x0a\x0a\ +" + +qt_resource_name = b"\ +\x00\x02\ +\x00\x00\x07\x84\ +\x00q\ +\x00t\ +\x00\x03\ +\x00\x00x<\ +\x00q\ +\x00m\x00l\ +\x00\x0c\ +\x0fN\xa7E\ +\x00C\ +\x00o\x00l\x00o\x00r\x00P\x00a\x00l\x00e\x00t\x00t\x00e\ +\x00\x05\ +\x00o\xa6S\ +\x00i\ +\x00c\x00o\x00n\x00s\ +\x00\x08\ +\x03\xc6T'\ +\x00p\ +\x00l\x00u\x00s\x00.\x00s\x00v\x00g\ +\x00\x0c\ +\x07\x11\xd4\xa7\ +\x00u\ +\x00s\x00e\x00r\x00M\x00a\x00s\x00k\x00.\x00s\x00v\x00g\ +\x00\x06\ +\x07\x87WG\ +\x00q\ +\x00t\x00.\x00p\x00n\x00g\ +\x00\x0a\ +\x08\xab\xd7\x87\ +\x00u\ +\x00p\x00d\x00a\x00t\x00e\x00.\x00s\x00v\x00g\ +\x00\x0a\ +\x0c\xad\x02\x87\ +\x00d\ +\x00e\x00l\x00e\x00t\x00e\x00.\x00s\x00v\x00g\ +\x00\x08\ +\x0b\x07W\xa7\ +\x00e\ +\x00d\x00i\x00t\x00.\x00s\x00v\x00g\ +\x00\x0e\ +\x05\x92p\xc7\ +\x00t\ +\x00e\x00s\x00t\x00s\x00e\x00r\x00v\x00e\x00r\x00.\x00p\x00n\x00g\ +\x00\x09\ +\x06\x98\x8e\xa7\ +\x00c\ +\x00l\x00o\x00s\x00e\x00.\x00s\x00v\x00g\ +\x00\x08\ +\x06\xb6W\xa7\ +\x00d\ +\x00o\x00t\x00s\x00.\x00s\x00v\x00g\ +\x00\x08\ +\x09\xc5UG\ +\x00u\ +\x00s\x00e\x00r\x00.\x00s\x00v\x00g\ +\x00\x06\ +\x07^Z\xc7\ +\x00o\ +\x00k\x00.\x00s\x00v\x00g\ +\x00\x0a\ +\x06\xc91\x07\ +\x00l\ +\x00o\x00g\x00o\x00u\x00t\x00.\x00s\x00v\x00g\ +\x00\x09\ +\x0e\x01\xbcg\ +\x00l\ +\x00o\x00g\x00i\x00n\x00.\x00s\x00v\x00g\ +" + +qt_resource_struct = b"\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x0a\x00\x02\x00\x00\x00\x01\x00\x00\x00\x03\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x16\x00\x02\x00\x00\x00\x01\x00\x00\x00\x04\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x004\x00\x02\x00\x00\x00\x0d\x00\x00\x00\x05\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00D\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ +\x00\x00\x01\x8d\x87\xa2.\x0a\ +\x00\x00\x00\xd4\x00\x00\x00\x00\x00\x01\x00\x00\x13\x96\ +\x00\x00\x01\x8d\x87\xa2.\x0a\ +\x00\x00\x00\xf6\x00\x00\x00\x00\x00\x01\x00\x00.-\ +\x00\x00\x01\x8d\x87\xa2.\x0a\ +\x00\x00\x01\x0e\x00\x00\x00\x00\x00\x01\x00\x00/c\ +\x00\x00\x01\x8d\x87\xa2.\x0a\ +\x00\x00\x01L\x00\x00\x00\x00\x00\x01\x00\x008\x08\ +\x00\x00\x01\x8d\x87\xa2.\x0a\ +\x00\x00\x00Z\x00\x00\x00\x00\x00\x01\x00\x00\x00\xc4\ +\x00\x00\x01\x8d\x87\xa2.\x0a\ +\x00\x00\x01:\x00\x00\x00\x00\x00\x01\x00\x007%\ +\x00\x00\x01\x8d\x87\xa2.\x0a\ +\x00\x00\x00x\x00\x00\x00\x00\x00\x01\x00\x00\x01\x5c\ +\x00\x00\x01\x8d\x87\xa2.\x0a\ +\x00\x00\x00\x8a\x00\x00\x00\x00\x00\x01\x00\x00\x0c\xf3\ +\x00\x00\x01\x8d\x87\xa2.\x0a\ +\x00\x00\x01$\x00\x00\x00\x00\x00\x01\x00\x003\xea\ +\x00\x00\x01\x8d\x87\xa2.\x0a\ +\x00\x00\x00\xbe\x00\x00\x00\x00\x00\x01\x00\x00\x11\x9b\ +\x00\x00\x01\x8d\x87\xa2.\x0a\ +\x00\x00\x00\xa4\x00\x00\x00\x00\x00\x01\x00\x00\x0f\xe4\ +\x00\x00\x01\x8d\x87\xa2.\x0a\ +\x00\x00\x01f\x00\x00\x00\x00\x00\x01\x00\x009b\ +\x00\x00\x01\x8d\x87\xa2.\x0a\ +" + +def qInitResources(): + QtCore.qRegisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) + +def qCleanupResources(): + QtCore.qUnregisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) + +qInitResources() diff --git a/examples/demos/colorpaletteclient/restservice.py b/examples/demos/colorpaletteclient/restservice.py new file mode 100644 index 00000000..d334ecd0 --- /dev/null +++ b/examples/demos/colorpaletteclient/restservice.py @@ -0,0 +1,53 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from PySide6.QtCore import Property, Signal, ClassInfo +from PySide6.QtNetwork import (QNetworkAccessManager, QRestAccessManager, + QNetworkRequestFactory, QSslSocket) +from PySide6.QtQml import QmlElement, QPyQmlParserStatus, ListProperty +from abstractresource import AbstractResource + +QML_IMPORT_NAME = "ColorPalette" +QML_IMPORT_MAJOR_VERSION = 1 + + +@QmlElement +@ClassInfo(DefaultProperty="resources") +class RestService(QPyQmlParserStatus): + + urlChanged = Signal() + + def __init__(self, parent=None): + super().__init__(parent) + self.m_resources = [] + self.m_qnam = QNetworkAccessManager() + self.m_qnam.setAutoDeleteReplies(True) + self.m_manager = QRestAccessManager(self.m_qnam) + self.m_serviceApi = QNetworkRequestFactory() + + @Property(str, notify=urlChanged) + def url(self): + return self.m_serviceApi.baseUrl() + + @url.setter + def url(self, url): + if self.m_serviceApi.baseUrl() != url: + self.m_serviceApi.setBaseUrl(url) + self.urlChanged.emit() + + @Property(bool, constant=True) + def sslSupported(self): + return QSslSocket.supportsSsl() + + def classBegin(self): + pass + + def componentComplete(self): + for resource in self.m_resources: + resource.setAccessManager(self.m_manager) + resource.setServiceApi(self.m_serviceApi) + + def appendResource(self, r): + self.m_resources.append(r) + + resources = ListProperty(AbstractResource, appendResource) diff --git a/examples/demos/documentviewer/jsonviewer/jsonviewer.py b/examples/demos/documentviewer/jsonviewer/jsonviewer.py index d582edd0..1e5a34ca 100644 --- a/examples/demos/documentviewer/jsonviewer/jsonviewer.py +++ b/examples/demos/documentviewer/jsonviewer/jsonviewer.py @@ -184,13 +184,13 @@ class JsonViewer(AbstractViewer): menu = self.addMenu("Json") tb = self.addToolBar("Json Actions") - zoomInIcon = QIcon.fromTheme("zoom-in") + zoomInIcon = QIcon.fromTheme(QIcon.ThemeIcon.ZoomIn) a = menu.addAction(zoomInIcon, "&+Expand all", self._tree.expandAll) tb.addAction(a) a.setPriority(QAction.LowPriority) a.setShortcut(QKeySequence.New) - zoomOutIcon = QIcon.fromTheme("zoom-out") + zoomOutIcon = QIcon.fromTheme(QIcon.ThemeIcon.ZoomOut) a = menu.addAction(zoomOutIcon, "&-Collapse all", self._tree.collapseAll) tb.addAction(a) a.setPriority(QAction.LowPriority) diff --git a/examples/demos/documentviewer/pdfviewer/pdfviewer.py b/examples/demos/documentviewer/pdfviewer/pdfviewer.py index 63d25bf7..d5695e4d 100644 --- a/examples/demos/documentviewer/pdfviewer/pdfviewer.py +++ b/examples/demos/documentviewer/pdfviewer/pdfviewer.py @@ -62,13 +62,17 @@ class PdfViewer(AbstractViewer): actionZoomIn = self._toolBar.addAction("Zoom in") actionZoomIn.setToolTip("Increase zoom level") - actionZoomIn.setIcon(QIcon(":/demos/documentviewer/images/zoom-in.png")) + icon = QIcon.fromTheme(QIcon.ThemeIcon.ZoomIn, + QIcon(":/demos/documentviewer/images/zoom-in.png")) + actionZoomIn.setIcon(icon) self._toolBar.addAction(actionZoomIn) actionZoomIn.triggered.connect(self.onActionZoomInTriggered) actionZoomOut = self._toolBar.addAction("Zoom out") actionZoomOut.setToolTip("Decrease zoom level") - actionZoomOut.setIcon(QIcon(":/demos/documentviewer/images/zoom-out.png")) + icon = QIcon.fromTheme(QIcon.ThemeIcon.ZoomIn, + QIcon(":/demos/documentviewer/images/zoom-out.png")) + actionZoomOut.setIcon(icon) self._toolBar.addAction(actionZoomOut) actionZoomOut.triggered.connect(self.onActionZoomOutTriggered) diff --git a/examples/demos/documentviewer/txtviewer/txtviewer.py b/examples/demos/documentviewer/txtviewer/txtviewer.py index 27f81ecb..ef5a38fc 100644 --- a/examples/demos/documentviewer/txtviewer/txtviewer.py +++ b/examples/demos/documentviewer/txtviewer/txtviewer.py @@ -28,7 +28,7 @@ class TxtViewer(AbstractViewer): def setupTxtUi(self): editMenu = self.addMenu("Edit") editToolBar = self.addToolBar("Edit") - cutIcon = QIcon.fromTheme("edit-cut", + cutIcon = QIcon.fromTheme(QIcon.ThemeIcon.EditCut, QIcon(":/demos/documentviewer/images/cut.png")) cutAct = QAction(cutIcon, "Cut", self) cutAct.setShortcuts(QKeySequence.Cut) @@ -37,7 +37,7 @@ class TxtViewer(AbstractViewer): editMenu.addAction(cutAct) editToolBar.addAction(cutAct) - copyIcon = QIcon.fromTheme("edit-copy", + copyIcon = QIcon.fromTheme(QIcon.ThemeIcon.EditCopy, QIcon(":/demos/documentviewer/images/copy.png")) copyAct = QAction(copyIcon, "Copy", self) copyAct.setShortcuts(QKeySequence.Copy) @@ -46,7 +46,7 @@ class TxtViewer(AbstractViewer): editMenu.addAction(copyAct) editToolBar.addAction(copyAct) - pasteIcon = QIcon.fromTheme("edit-paste", + pasteIcon = QIcon.fromTheme(QIcon.ThemeIcon.EditPaste, QIcon(":/demos/documentviewer/images/paste.png")) pasteAct = QAction(pasteIcon, "Paste", self) pasteAct.setShortcuts(QKeySequence.Paste) diff --git a/examples/demos/documentviewer/ui_mainwindow.py b/examples/demos/documentviewer/ui_mainwindow.py index 69ac0914..5773ae5b 100644 --- a/examples/demos/documentviewer/ui_mainwindow.py +++ b/examples/demos/documentviewer/ui_mainwindow.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'mainwindow.ui' ## -## Created by: Qt User Interface Compiler version 6.5.3 +## Created by: Qt User Interface Compiler version 6.7.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -110,7 +110,7 @@ class Ui_MainWindow(object): self.splitter.addWidget(self.tabWidget) self.scrollArea = QScrollArea(self.splitter) self.scrollArea.setObjectName(u"scrollArea") - sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + sizePolicy = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.scrollArea.sizePolicy().hasHeightForWidth()) @@ -142,7 +142,7 @@ class Ui_MainWindow(object): MainWindow.setStatusBar(self.statusbar) self.mainToolBar = QToolBar(MainWindow) self.mainToolBar.setObjectName(u"mainToolBar") - MainWindow.addToolBar(Qt.TopToolBarArea, self.mainToolBar) + MainWindow.addToolBar(Qt.ToolBarArea.TopToolBarArea, self.mainToolBar) self.menubar.addAction(self.qtFileMenu.menuAction()) self.menubar.addAction(self.menuHelp.menuAction()) diff --git a/examples/designer/taskmenuextension/doc/taskmenuextension.rst b/examples/designer/taskmenuextension/doc/taskmenuextension.rst index a1b584fb..dd7fe167 100644 --- a/examples/designer/taskmenuextension/doc/taskmenuextension.rst +++ b/examples/designer/taskmenuextension/doc/taskmenuextension.rst @@ -1,5 +1,7 @@ -Task Menu Extension (Designer) -============================== +.. _task-menu-extension-example: + +Task Menu Extension Example +=========================== This example shows how to add custom widgets to Qt Designer, which can be launched with `pyside6-designer`, and to extend diff --git a/examples/graphs/2d/hellographs/HelloGraphs/Main.qml b/examples/graphs/2d/hellographs/HelloGraphs/Main.qml new file mode 100644 index 00000000..b1844aec --- /dev/null +++ b/examples/graphs/2d/hellographs/HelloGraphs/Main.qml @@ -0,0 +1,153 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Layouts +import QtGraphs + +Item { + id: mainView + width: 1280 + height: 720 + + RowLayout { + id: graphsRow + + readonly property real margin: mainView.width * 0.02 + + anchors.fill: parent + anchors.margins: margin + spacing: margin + + Rectangle { + Layout.fillHeight: true + Layout.fillWidth: true + color: "#262626" + border.color: "#4d4d4d" + border.width: 1 + radius: graphsRow.margin + //! [bargraph] + GraphsView { + anchors.fill: parent + anchors.margins: 16 + theme: GraphTheme { + colorTheme: GraphTheme.ColorThemeDark + } + //! [bargraph] + //! [barseries] + BarSeries { + axisX: BarCategoryAxis { + categories: [2024, 2025, 2026] + gridVisible: false + minorGridVisible: false + } + axisY: ValueAxis { + min: 20 + max: 100 + tickInterval: 10 + minorTickCount: 9 + } + //! [barseries] + //! [barset] + BarSet { + values: [82, 50, 75] + borderWidth: 2 + color: "#373F26" + borderColor: "#DBEB00" + } + //! [barset] + } + } + } + + Rectangle { + Layout.fillHeight: true + Layout.fillWidth: true + color: "#262626" + border.color: "#4d4d4d" + border.width: 1 + radius: graphsRow.margin + + //! [linegraph] + GraphsView { + anchors.fill: parent + anchors.margins: 16 + theme: GraphTheme { + readonly property color c1: "#DBEB00" + readonly property color c2: "#373F26" + readonly property color c3: Qt.lighter(c2, 1.5) + colorTheme: GraphTheme.ColorThemeDark + gridMajorBarsColor: c3 + gridMinorBarsColor: c2 + axisXMajorColor: c3 + axisYMajorColor: c3 + axisXMinorColor: c2 + axisYMinorColor: c2 + axisXLabelsColor: c1 + axisYLabelsColor: c1 + } + //! [linegraph] + + //! [linemarker] + component Marker : Rectangle { + width: 16 + height: 16 + color: "#ffffff" + radius: width * 0.5 + border.width: 4 + border.color: "#000000" + } + //! [linemarker] + + //! [lineseriestheme] + SeriesTheme { + id: seriesTheme + colors: ["#2CDE85", "#DBEB00"] + } + //! [lineseriestheme] + + //! [lineseries1] + LineSeries { + id: lineSeries1 + theme: seriesTheme + axisX: ValueAxis { + max: 5 + tickInterval: 1 + minorTickCount: 9 + labelDecimals: 1 + } + axisY: ValueAxis { + max: 10 + tickInterval: 1 + minorTickCount: 4 + labelDecimals: 1 + } + width: 4 + pointMarker: Marker { } + XYPoint { x: 0; y: 0 } + XYPoint { x: 1; y: 2.1 } + XYPoint { x: 2; y: 3.3 } + XYPoint { x: 3; y: 2.1 } + XYPoint { x: 4; y: 4.9 } + XYPoint { x: 5; y: 3.0 } + } + //! [lineseries1] + + //! [lineseries2] + LineSeries { + id: lineSeries2 + theme: seriesTheme + width: 4 + pointMarker: Marker { } + XYPoint { x: 0; y: 5.0 } + XYPoint { x: 1; y: 3.3 } + XYPoint { x: 2; y: 7.1 } + XYPoint { x: 3; y: 7.5 } + XYPoint { x: 4; y: 6.1 } + XYPoint { x: 5; y: 3.2 } + } + //! [lineseries2] + } + } + } +} diff --git a/examples/graphs/2d/hellographs/HelloGraphs/qmldir b/examples/graphs/2d/hellographs/HelloGraphs/qmldir new file mode 100644 index 00000000..007f5fb1 --- /dev/null +++ b/examples/graphs/2d/hellographs/HelloGraphs/qmldir @@ -0,0 +1,2 @@ +module HelloGraphs +Main 1.0 Main.qml diff --git a/examples/graphs/2d/hellographs/doc/hellographs.rst b/examples/graphs/2d/hellographs/doc/hellographs.rst new file mode 100644 index 00000000..d0820c3b --- /dev/null +++ b/examples/graphs/2d/hellographs/doc/hellographs.rst @@ -0,0 +1,51 @@ +HelloGraphs Example +=================== + +The example shows how to make a simple 2D bar graph and line graph. + +BarGraph +-------- + +The first graph in the example is a bar graph. Creating it starts with a GraphsView +component and setting the theme to one which is suitable on +dark backgrounds. This theme adjusts the graph background grid and axis lines and +labels. + +To make this a bar graph, add a ``BarSeries.`` The X axis of the series is a +``BarCategoryAxis`` with 3 categories. We hide both the vertical grid and the +axis lines. The Y axis of the series is ``ValueAxis`` with visible range +between 20 and 100. Major ticks with labels will be shown on every 10 values +using the ``tickInterval`` property. Minor ticks will be shown on every 1 +values setting the ``minorTickCount`` propertyt to 9, which means that between +every major ticks there will be 9 minor ones. + +Then data is added into ``BarSeries`` using ``BarSet.`` There are 3 bars, and we define +custom bars color and border properties. These properties will override the possible +theme set for the ``AbstractSeries.`` + +LineGraph +--------- + +The second graph of the example is a line graph. It also starts by defining a +``GraphsView`` element. A custom ``GraphTheme`` is created to get a custom appearance. +``GraphTheme`` offers quite a wide range of customization possibilities for the background +grid and axis, which get applied after the ``colorTheme``. + +A custom ``Marker`` component is used to visualize the data points. + +The previous bar graph didn't define a separate ``SeriesTheme``, so it uses the +default theme. This line graph uses a custom theme with the desired line colors. + +To make this a line graph, add a ``LineSeries.`` The first series defines +``axisX`` and ``axisY`` for this graph. It also sets the ``pointMarker`` to use +the custom ``Marker`` component that was created earlier. Data points are added +using ``XYPoint`` elements. + +The second line series is similar to the first. The ``axisX`` and ``axisY`` +don't need to be defined as the graph already contains them. As this is the +second ``LineSeries`` inside the ``GraphsView``, second color from the +``seriesTheme`` gets automatically picked. + +.. image:: hellographs.webp + :width: 1293 + :alt: HelloGraphs Screenshot diff --git a/examples/graphs/2d/hellographs/doc/hellographs.webp b/examples/graphs/2d/hellographs/doc/hellographs.webp new file mode 100644 index 00000000..3e766641 Binary files /dev/null and b/examples/graphs/2d/hellographs/doc/hellographs.webp differ diff --git a/examples/graphs/2d/hellographs/hellographs.pyproject b/examples/graphs/2d/hellographs/hellographs.pyproject new file mode 100644 index 00000000..e8e8cb22 --- /dev/null +++ b/examples/graphs/2d/hellographs/hellographs.pyproject @@ -0,0 +1,3 @@ +{ + "files": ["main.py", "HelloGraphs/Main.qml", "HelloGraphs/qmldir"] +} diff --git a/examples/graphs/2d/hellographs/main.py b/examples/graphs/2d/hellographs/main.py new file mode 100644 index 00000000..acc349be --- /dev/null +++ b/examples/graphs/2d/hellographs/main.py @@ -0,0 +1,22 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +"""PySide6 port of the Qt Hello Graphs example from Qt v6.x""" + +from pathlib import Path +import sys +from PySide6.QtGui import QGuiApplication +from PySide6.QtQuick import QQuickView + + +if __name__ == '__main__': + app = QGuiApplication(sys.argv) + + viewer = QQuickView() + viewer.engine().addImportPath(Path(__file__).parent) + viewer.setColor("black") + viewer.loadFromModule("HelloGraphs", "Main") + viewer.show() + r = app.exec() + del viewer + sys.exit(r) diff --git a/examples/graphs/3d/minimalsurfacegraph/doc/minimalsurfacegraph.rst b/examples/graphs/3d/minimalsurfacegraph/doc/minimalsurfacegraph.rst new file mode 100644 index 00000000..bfc7a044 --- /dev/null +++ b/examples/graphs/3d/minimalsurfacegraph/doc/minimalsurfacegraph.rst @@ -0,0 +1,4 @@ +Minimal Surface Example +======================= + +The example shows the minimal code to create a surface. diff --git a/examples/graphs/3d/minimalsurfacegraph/main.py b/examples/graphs/3d/minimalsurfacegraph/main.py new file mode 100644 index 00000000..5fb4b447 --- /dev/null +++ b/examples/graphs/3d/minimalsurfacegraph/main.py @@ -0,0 +1,54 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import sys + +from PySide6.QtCore import QSize +from PySide6.QtGui import QVector3D +from PySide6.QtGraphs import (Q3DSurface, QSurfaceDataItem, + QSurface3DSeries) +from PySide6.QtWidgets import QApplication +from PySide6.QtQuickWidgets import QQuickWidget + + +DESCRIPTION = """Minimal Qt Graphs Surface Example + +Use the mouse wheel to zoom. Rotate using the right mouse button. +""" + + +if __name__ == '__main__': + app = QApplication(sys.argv) + + print(DESCRIPTION) + + surface = Q3DSurface() + axis = surface.axisX() + axis.setTitle("X") + axis.setTitleVisible(True) + axis = surface.axisY() + axis.setTitle("Y") + axis.setTitleVisible(True) + axis = surface.axisZ() + axis.setTitle("Z") + axis.setTitleVisible(True) + + data = [] + data_row1 = [QSurfaceDataItem(QVector3D(0, 0.1, 0.5)), + QSurfaceDataItem(QVector3D(1, 0.5, 0.5))] + data.append(data_row1) + data_row2 = [QSurfaceDataItem(QVector3D(0, 1.8, 1)), + QSurfaceDataItem(QVector3D(1, 1.2, 1))] + data.append(data_row2) + + series = QSurface3DSeries() + series.dataProxy().resetArray(data) + surface.addSeries(series) + + available_height = app.primaryScreen().availableGeometry().height() + width = available_height * 4 / 5 + surface.resize(QSize(width, width)) + surface.setResizeMode(QQuickWidget.SizeRootObjectToView) + surface.show() + + sys.exit(app.exec()) diff --git a/examples/graphs/3d/widgetgallery/axesinputhandler.py b/examples/graphs/3d/widgetgallery/axesinputhandler.py new file mode 100644 index 00000000..4c420297 --- /dev/null +++ b/examples/graphs/3d/widgetgallery/axesinputhandler.py @@ -0,0 +1,100 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from enum import Enum +from math import sin, cos, degrees + +from PySide6.QtCore import Qt +from PySide6.QtGraphs import QAbstract3DGraph, Q3DInputHandler + + +class InputState(Enum): + StateNormal = 0 + StateDraggingX = 1 + StateDraggingZ = 2 + StateDraggingY = 3 + + +class AxesInputHandler(Q3DInputHandler): + + def __init__(self, graph, parent=None): + super().__init__(parent) + self._mousePressed = False + self._state = InputState.StateNormal + self._axisX = None + self._axisZ = None + self._axisY = None + self._speedModifier = 15.0 + + # Connect to the item selection signal from graph + graph.selectedElementChanged.connect(self.handleElementSelected) + + def setAxes(self, axisX, axisZ, axisY): + self._axisX = axisX + self._axisZ = axisZ + self._axisY = axisY + + def setDragSpeedModifier(self, modifier): + self._speedModifier = modifier + + def mousePressEvent(self, event, mousePos): + super().mousePressEvent(event, mousePos) + if Qt.LeftButton == event.button(): + self._mousePressed = True + + def mouseMoveEvent(self, event, mousePos): + # Check if we're trying to drag axis label + if self._mousePressed and self._state != InputState.StateNormal: + self.setPreviousInputPos(self.inputPosition()) + self.setInputPosition(mousePos) + self.handleAxisDragging() + else: + super().mouseMoveEvent(event, mousePos) + + def mouseReleaseEvent(self, event, mousePos): + super().mouseReleaseEvent(event, mousePos) + self._mousePressed = False + self._state = InputState.StateNormal + + def handleElementSelected(self, type): + if type == QAbstract3DGraph.ElementAxisXLabel: + self._state = InputState.StateDraggingX + elif type == QAbstract3DGraph.ElementAxisYLabel: + self._state = InputState.StateDraggingY + elif type == QAbstract3DGraph.ElementAxisZLabel: + self._state = InputState.StateDraggingZ + else: + self._state = InputState.StateNormal + + def handleAxisDragging(self): + distance = 0.0 + # Get scene orientation from active camera + xRotation = self.cameraXRotation() + yRotation = self.cameraYRotation() + + # Calculate directional drag multipliers based on rotation + xMulX = cos(degrees(xRotation)) + xMulY = sin(degrees(xRotation)) + zMulX = sin(degrees(xRotation)) + zMulY = cos(degrees(xRotation)) + + # Get the drag amount + move = self.inputPosition() - self.previousInputPos() + + # Flip the effect of y movement if we're viewing from below + yMove = -move.y() if yRotation < 0 else move.y() + + # Adjust axes + if self._state == InputState.StateDraggingX: + distance = (move.x() * xMulX - yMove * xMulY) / self._speedModifier + self._axisX.setRange(self._axisX.min() - distance, + self._axisX.max() - distance) + elif self._state == InputState.StateDraggingZ: + distance = (move.x() * zMulX + yMove * zMulY) / self._speedModifier + self._axisZ.setRange(self._axisZ.min() + distance, + self._axisZ.max() + distance) + elif self._state == InputState.StateDraggingY: + # No need to use adjusted y move here + distance = move.y() / self._speedModifier + self._axisY.setRange(self._axisY.min() + distance, + self._axisY.max() + distance) diff --git a/examples/graphs/3d/widgetgallery/bargraph.py b/examples/graphs/3d/widgetgallery/bargraph.py new file mode 100644 index 00000000..822acb4a --- /dev/null +++ b/examples/graphs/3d/widgetgallery/bargraph.py @@ -0,0 +1,272 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from graphmodifier import GraphModifier + +from PySide6.QtCore import QObject, Qt +from PySide6.QtGui import QFont +from PySide6.QtWidgets import (QButtonGroup, QCheckBox, QComboBox, QFontComboBox, + QLabel, QPushButton, QHBoxLayout, QSizePolicy, + QRadioButton, QSlider, QVBoxLayout, QWidget) +from PySide6.QtQuickWidgets import QQuickWidget +from PySide6.QtGraphs import (QAbstract3DGraph, QAbstract3DSeries, Q3DBars) + + +class BarGraph(QObject): + + def __init__(self, minimum_graph_size, maximum_graph_size): + super().__init__() + self._barsGraph = Q3DBars() + self._barsWidget = QWidget() + hLayout = QHBoxLayout(self._barsWidget) + self._barsGraph.setMinimumSize(minimum_graph_size) + self._barsGraph.setMaximumSize(maximum_graph_size) + self._barsGraph.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + self._barsGraph.setFocusPolicy(Qt.StrongFocus) + self._barsGraph.setResizeMode(QQuickWidget.SizeRootObjectToView) + hLayout.addWidget(self._barsGraph, 1) + + vLayout = QVBoxLayout() + hLayout.addLayout(vLayout) + + themeList = QComboBox(self._barsWidget) + themeList.addItem("Qt") + themeList.addItem("Primary Colors") + themeList.addItem("Digia") + themeList.addItem("Stone Moss") + themeList.addItem("Army Blue") + themeList.addItem("Retro") + themeList.addItem("Ebony") + themeList.addItem("Isabelle") + themeList.setCurrentIndex(0) + + labelButton = QPushButton(self._barsWidget) + labelButton.setText("Change label style") + + smoothCheckBox = QCheckBox(self._barsWidget) + smoothCheckBox.setText("Smooth bars") + smoothCheckBox.setChecked(False) + + barStyleList = QComboBox(self._barsWidget) + barStyleList.addItem("Bar", QAbstract3DSeries.Mesh.Bar) + barStyleList.addItem("Pyramid", QAbstract3DSeries.Mesh.Pyramid) + barStyleList.addItem("Cone", QAbstract3DSeries.Mesh.Cone) + barStyleList.addItem("Cylinder", QAbstract3DSeries.Mesh.Cylinder) + barStyleList.addItem("Bevel bar", QAbstract3DSeries.Mesh.BevelBar) + barStyleList.addItem("Sphere", QAbstract3DSeries.Mesh.Sphere) + barStyleList.setCurrentIndex(4) + + cameraButton = QPushButton(self._barsWidget) + cameraButton.setText("Change camera preset") + + zoomToSelectedButton = QPushButton(self._barsWidget) + zoomToSelectedButton.setText("Zoom to selected bar") + + selectionModeList = QComboBox(self._barsWidget) + selectionModeList.addItem("None", QAbstract3DGraph.SelectionNone) + selectionModeList.addItem("Bar", QAbstract3DGraph.SelectionItem) + selectionModeList.addItem("Row", QAbstract3DGraph.SelectionRow) + sel = QAbstract3DGraph.SelectionItemAndRow + selectionModeList.addItem("Bar and Row", sel) + selectionModeList.addItem("Column", QAbstract3DGraph.SelectionColumn) + sel = QAbstract3DGraph.SelectionItemAndColumn + selectionModeList.addItem("Bar and Column", sel) + sel = QAbstract3DGraph.SelectionRowAndColumn + selectionModeList.addItem("Row and Column", sel) + sel = QAbstract3DGraph.SelectionItemRowAndColumn + selectionModeList.addItem("Bar, Row and Column", sel) + sel = QAbstract3DGraph.SelectionSlice | QAbstract3DGraph.SelectionRow + selectionModeList.addItem("Slice into Row", sel) + sel = QAbstract3DGraph.SelectionSlice | QAbstract3DGraph.SelectionItemAndRow + selectionModeList.addItem("Slice into Row and Item", sel) + sel = QAbstract3DGraph.SelectionSlice | QAbstract3DGraph.SelectionColumn + selectionModeList.addItem("Slice into Column", sel) + sel = (QAbstract3DGraph.SelectionSlice + | QAbstract3DGraph.SelectionItemAndColumn) + selectionModeList.addItem("Slice into Column and Item", sel) + sel = (QAbstract3DGraph.SelectionItemRowAndColumn + | QAbstract3DGraph.SelectionMultiSeries) + selectionModeList.addItem("Multi: Bar, Row, Col", sel) + sel = (QAbstract3DGraph.SelectionSlice + | QAbstract3DGraph.SelectionItemAndRow + | QAbstract3DGraph.SelectionMultiSeries) + selectionModeList.addItem("Multi, Slice: Row, Item", sel) + sel = (QAbstract3DGraph.SelectionSlice + | QAbstract3DGraph.SelectionItemAndColumn + | QAbstract3DGraph.SelectionMultiSeries) + selectionModeList.addItem("Multi, Slice: Col, Item", sel) + selectionModeList.setCurrentIndex(1) + + backgroundCheckBox = QCheckBox(self._barsWidget) + backgroundCheckBox.setText("Show background") + backgroundCheckBox.setChecked(False) + + gridCheckBox = QCheckBox(self._barsWidget) + gridCheckBox.setText("Show grid") + gridCheckBox.setChecked(True) + + seriesCheckBox = QCheckBox(self._barsWidget) + seriesCheckBox.setText("Show second series") + seriesCheckBox.setChecked(False) + + reverseValueAxisCheckBox = QCheckBox(self._barsWidget) + reverseValueAxisCheckBox.setText("Reverse value axis") + reverseValueAxisCheckBox.setChecked(False) + + reflectionCheckBox = QCheckBox(self._barsWidget) + reflectionCheckBox.setText("Show reflections") + reflectionCheckBox.setChecked(False) + + rotationSliderX = QSlider(Qt.Horizontal, self._barsWidget) + rotationSliderX.setTickInterval(30) + rotationSliderX.setTickPosition(QSlider.TicksBelow) + rotationSliderX.setMinimum(-180) + rotationSliderX.setValue(0) + rotationSliderX.setMaximum(180) + rotationSliderY = QSlider(Qt.Horizontal, self._barsWidget) + rotationSliderY.setTickInterval(15) + rotationSliderY.setTickPosition(QSlider.TicksAbove) + rotationSliderY.setMinimum(-90) + rotationSliderY.setValue(0) + rotationSliderY.setMaximum(90) + + fontSizeSlider = QSlider(Qt.Horizontal, self._barsWidget) + fontSizeSlider.setTickInterval(10) + fontSizeSlider.setTickPosition(QSlider.TicksBelow) + fontSizeSlider.setMinimum(1) + fontSizeSlider.setValue(30) + fontSizeSlider.setMaximum(100) + + fontList = QFontComboBox(self._barsWidget) + fontList.setCurrentFont(QFont("Times New Roman")) + + shadowQuality = QComboBox(self._barsWidget) + shadowQuality.addItem("None") + shadowQuality.addItem("Low") + shadowQuality.addItem("Medium") + shadowQuality.addItem("High") + shadowQuality.addItem("Low Soft") + shadowQuality.addItem("Medium Soft") + shadowQuality.addItem("High Soft") + shadowQuality.setCurrentIndex(5) + + rangeList = QComboBox(self._barsWidget) + rangeList.addItem("2015") + rangeList.addItem("2016") + rangeList.addItem("2017") + rangeList.addItem("2018") + rangeList.addItem("2019") + rangeList.addItem("2020") + rangeList.addItem("2021") + rangeList.addItem("2022") + rangeList.addItem("All") + rangeList.setCurrentIndex(8) + + axisTitlesVisibleCB = QCheckBox(self._barsWidget) + axisTitlesVisibleCB.setText("Axis titles visible") + axisTitlesVisibleCB.setChecked(True) + + axisTitlesFixedCB = QCheckBox(self._barsWidget) + axisTitlesFixedCB.setText("Axis titles fixed") + axisTitlesFixedCB.setChecked(True) + + axisLabelRotationSlider = QSlider(Qt.Horizontal, self._barsWidget) + axisLabelRotationSlider.setTickInterval(10) + axisLabelRotationSlider.setTickPosition(QSlider.TicksBelow) + axisLabelRotationSlider.setMinimum(0) + axisLabelRotationSlider.setValue(30) + axisLabelRotationSlider.setMaximum(90) + + modeGroup = QButtonGroup(self._barsWidget) + modeWeather = QRadioButton("Temperature Data", self._barsWidget) + modeWeather.setChecked(True) + modeCustomProxy = QRadioButton("Custom Proxy Data", self._barsWidget) + modeGroup.addButton(modeWeather) + modeGroup.addButton(modeCustomProxy) + + vLayout.addWidget(QLabel("Rotate horizontally")) + vLayout.addWidget(rotationSliderX, 0, Qt.AlignTop) + vLayout.addWidget(QLabel("Rotate vertically")) + vLayout.addWidget(rotationSliderY, 0, Qt.AlignTop) + vLayout.addWidget(labelButton, 0, Qt.AlignTop) + vLayout.addWidget(cameraButton, 0, Qt.AlignTop) + vLayout.addWidget(zoomToSelectedButton, 0, Qt.AlignTop) + vLayout.addWidget(backgroundCheckBox) + vLayout.addWidget(gridCheckBox) + vLayout.addWidget(smoothCheckBox) + vLayout.addWidget(reflectionCheckBox) + vLayout.addWidget(seriesCheckBox) + vLayout.addWidget(reverseValueAxisCheckBox) + vLayout.addWidget(axisTitlesVisibleCB) + vLayout.addWidget(axisTitlesFixedCB) + vLayout.addWidget(QLabel("Show year")) + vLayout.addWidget(rangeList) + vLayout.addWidget(QLabel("Change bar style")) + vLayout.addWidget(barStyleList) + vLayout.addWidget(QLabel("Change selection mode")) + vLayout.addWidget(selectionModeList) + vLayout.addWidget(QLabel("Change theme")) + vLayout.addWidget(themeList) + vLayout.addWidget(QLabel("Adjust shadow quality")) + vLayout.addWidget(shadowQuality) + vLayout.addWidget(QLabel("Change font")) + vLayout.addWidget(fontList) + vLayout.addWidget(QLabel("Adjust font size")) + vLayout.addWidget(fontSizeSlider) + vLayout.addWidget(QLabel("Axis label rotation")) + vLayout.addWidget(axisLabelRotationSlider, 0, Qt.AlignTop) + vLayout.addWidget(modeWeather, 0, Qt.AlignTop) + vLayout.addWidget(modeCustomProxy, 1, Qt.AlignTop) + + self._modifier = GraphModifier(self._barsGraph, self) + + rotationSliderX.valueChanged.connect(self._modifier.rotateX) + rotationSliderY.valueChanged.connect(self._modifier.rotateY) + + labelButton.clicked.connect(self._modifier.changeLabelBackground) + cameraButton.clicked.connect(self._modifier.changePresetCamera) + zoomToSelectedButton.clicked.connect(self._modifier.zoomToSelectedBar) + + backgroundCheckBox.stateChanged.connect(self._modifier.setBackgroundEnabled) + gridCheckBox.stateChanged.connect(self._modifier.setGridEnabled) + smoothCheckBox.stateChanged.connect(self._modifier.setSmoothBars) + seriesCheckBox.stateChanged.connect(self._modifier.setSeriesVisibility) + reverseValueAxisCheckBox.stateChanged.connect(self._modifier.setReverseValueAxis) + reflectionCheckBox.stateChanged.connect(self._modifier.setReflection) + + self._modifier.backgroundEnabledChanged.connect(backgroundCheckBox.setChecked) + self._modifier.gridEnabledChanged.connect(gridCheckBox.setChecked) + + rangeList.currentIndexChanged.connect(self._modifier.changeRange) + + barStyleList.currentIndexChanged.connect(self._modifier.changeStyle) + + selectionModeList.currentIndexChanged.connect(self._modifier.changeSelectionMode) + + themeList.currentIndexChanged.connect(self._modifier.changeTheme) + + shadowQuality.currentIndexChanged.connect(self._modifier.changeShadowQuality) + + self._modifier.shadowQualityChanged.connect(shadowQuality.setCurrentIndex) + self._barsGraph.shadowQualityChanged.connect(self._modifier.shadowQualityUpdatedByVisual) + + fontSizeSlider.valueChanged.connect(self._modifier.changeFontSize) + fontList.currentFontChanged.connect(self._modifier.changeFont) + + self._modifier.fontSizeChanged.connect(fontSizeSlider.setValue) + self._modifier.fontChanged.connect(fontList.setCurrentFont) + + axisTitlesVisibleCB.stateChanged.connect(self._modifier.setAxisTitleVisibility) + axisTitlesFixedCB.stateChanged.connect(self._modifier.setAxisTitleFixed) + axisLabelRotationSlider.valueChanged.connect(self._modifier.changeLabelRotation) + + modeWeather.toggled.connect(self._modifier.setDataModeToWeather) + modeCustomProxy.toggled.connect(self._modifier.setDataModeToCustom) + modeWeather.toggled.connect(seriesCheckBox.setEnabled) + modeWeather.toggled.connect(rangeList.setEnabled) + modeWeather.toggled.connect(axisTitlesVisibleCB.setEnabled) + modeWeather.toggled.connect(axisTitlesFixedCB.setEnabled) + modeWeather.toggled.connect(axisLabelRotationSlider.setEnabled) + + def barsWidget(self): + return self._barsWidget diff --git a/examples/graphs/3d/widgetgallery/custominputhandler.py b/examples/graphs/3d/widgetgallery/custominputhandler.py new file mode 100644 index 00000000..15fe00e7 --- /dev/null +++ b/examples/graphs/3d/widgetgallery/custominputhandler.py @@ -0,0 +1,177 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from enum import Enum +from math import sin, cos, degrees + +from PySide6.QtCore import Qt +from PySide6.QtGraphs import (QAbstract3DGraph, Q3DInputHandler) + + +class InputState(Enum): + StateNormal = 0 + StateDraggingX = 1 + StateDraggingZ = 2 + StateDraggingY = 3 + + +class CustomInputHandler(Q3DInputHandler): + + def __init__(self, graph, parent=None): + super().__init__(parent) + self._highlight = None + self._mousePressed = False + self._state = InputState.StateNormal + self._axisX = None + self._axisY = None + self._axisZ = None + self._speedModifier = 20.0 + self._aspectRatio = 0.0 + self._axisXMinValue = 0.0 + self._axisXMaxValue = 0.0 + self._axisXMinRange = 0.0 + self._axisZMinValue = 0.0 + self._axisZMaxValue = 0.0 + self._axisZMinRange = 0.0 + self._areaMinValue = 0.0 + self._areaMaxValue = 0.0 + + # Connect to the item selection signal from graph + graph.selectedElementChanged.connect(self.handleElementSelected) + + def setAspectRatio(self, ratio): + self._aspectRatio = ratio + + def setHighlightSeries(self, series): + self._highlight = series + + def setDragSpeedModifier(self, modifier): + self._speedModifier = modifier + + def setLimits(self, min, max, minRange): + self._areaMinValue = min + self._areaMaxValue = max + self._axisXMinValue = self._areaMinValue + self._axisXMaxValue = self._areaMaxValue + self._axisZMinValue = self._areaMinValue + self._axisZMaxValue = self._areaMaxValue + self._axisXMinRange = minRange + self._axisZMinRange = minRange + + def setAxes(self, axisX, axisY, axisZ): + self._axisX = axisX + self._axisY = axisY + self._axisZ = axisZ + + def mousePressEvent(self, event, mousePos): + if Qt.LeftButton == event.button(): + self._highlight.setVisible(False) + self._mousePressed = True + super().mousePressEvent(event, mousePos) + + def wheelEvent(self, event): + delta = float(event.angleDelta().y()) + + self._axisXMinValue += delta + self._axisXMaxValue -= delta + self._axisZMinValue += delta + self._axisZMaxValue -= delta + self.checkConstraints() + + y = (self._axisXMaxValue - self._axisXMinValue) * self._aspectRatio + + self._axisX.setRange(self._axisXMinValue, self._axisXMaxValue) + self._axisY.setRange(100.0, y) + self._axisZ.setRange(self._axisZMinValue, self._axisZMaxValue) + + def mouseMoveEvent(self, event, mousePos): + # Check if we're trying to drag axis label + if self._mousePressed and self._state != InputState.StateNormal: + self.setPreviousInputPos(self.inputPosition()) + self.setInputPosition(mousePos) + self.handleAxisDragging() + else: + super().mouseMoveEvent(event, mousePos) + + def mouseReleaseEvent(self, event, mousePos): + super().mouseReleaseEvent(event, mousePos) + self._mousePressed = False + self._state = InputState.StateNormal + + def handleElementSelected(self, type): + if type == QAbstract3DGraph.ElementAxisXLabel: + self._state = InputState.StateDraggingX + elif type == QAbstract3DGraph.ElementAxisZLabel: + self._state = InputState.StateDraggingZ + else: + self._state = InputState.StateNormal + + def handleAxisDragging(self): + distance = 0.0 + + # Get scene orientation from active camera + xRotation = self.scene().cameraXRotation() + + # Calculate directional drag multipliers based on rotation + xMulX = cos(degrees(xRotation)) + xMulY = sin(degrees(xRotation)) + zMulX = xMulY + zMulY = xMulX + + # Get the drag amount + move = self.inputPosition() - self.previousInputPos() + + # Adjust axes + if self._state == InputState.StateDraggingX: + distance = (move.x() * xMulX - move.y() * xMulY) * self._speedModifier + self._axisXMinValue -= distance + self._axisXMaxValue -= distance + if self._axisXMinValue < self._areaMinValue: + dist = self._axisXMaxValue - self._axisXMinValue + self._axisXMinValue = self._areaMinValue + self._axisXMaxValue = self._axisXMinValue + dist + + if self._axisXMaxValue > self._areaMaxValue: + dist = self._axisXMaxValue - self._axisXMinValue + self._axisXMaxValue = self._areaMaxValue + self._axisXMinValue = self._axisXMaxValue - dist + + self._axisX.setRange(self._axisXMinValue, self._axisXMaxValue) + elif self._state == InputState.StateDraggingZ: + distance = (move.x() * zMulX + move.y() * zMulY) * self._speedModifier + self._axisZMinValue += distance + self._axisZMaxValue += distance + if self._axisZMinValue < self._areaMinValue: + dist = self._axisZMaxValue - self._axisZMinValue + self._axisZMinValue = self._areaMinValue + self._axisZMaxValue = self._axisZMinValue + dist + + if self._axisZMaxValue > self._areaMaxValue: + dist = self._axisZMaxValue - self._axisZMinValue + self._axisZMaxValue = self._areaMaxValue + self._axisZMinValue = self._axisZMaxValue - dist + + self._axisZ.setRange(self._axisZMinValue, self._axisZMaxValue) + + def checkConstraints(self): + if self._axisXMinValue < self._areaMinValue: + self._axisXMinValue = self._areaMinValue + if self._axisXMaxValue > self._areaMaxValue: + self._axisXMaxValue = self._areaMaxValue + # Don't allow too much zoom in + range = self._axisXMaxValue - self._axisXMinValue + if range < self._axisXMinRange: + adjust = (self._axisXMinRange - range) / 2.0 + self._axisXMinValue -= adjust + self._axisXMaxValue += adjust + + if self._axisZMinValue < self._areaMinValue: + self._axisZMinValue = self._areaMinValue + if self._axisZMaxValue > self._areaMaxValue: + self._axisZMaxValue = self._areaMaxValue + # Don't allow too much zoom in + range = self._axisZMaxValue - self._axisZMinValue + if range < self._axisZMinRange: + adjust = (self._axisZMinRange - range) / 2.0 + self._axisZMinValue -= adjust + self._axisZMaxValue += adjust diff --git a/examples/graphs/3d/widgetgallery/data/layer_1.png b/examples/graphs/3d/widgetgallery/data/layer_1.png new file mode 100644 index 00000000..9138c710 Binary files /dev/null and b/examples/graphs/3d/widgetgallery/data/layer_1.png differ diff --git a/examples/graphs/3d/widgetgallery/data/layer_2.png b/examples/graphs/3d/widgetgallery/data/layer_2.png new file mode 100644 index 00000000..61631ae8 Binary files /dev/null and b/examples/graphs/3d/widgetgallery/data/layer_2.png differ diff --git a/examples/graphs/3d/widgetgallery/data/layer_3.png b/examples/graphs/3d/widgetgallery/data/layer_3.png new file mode 100644 index 00000000..066ffbe7 Binary files /dev/null and b/examples/graphs/3d/widgetgallery/data/layer_3.png differ diff --git a/examples/graphs/3d/widgetgallery/data/license.txt b/examples/graphs/3d/widgetgallery/data/license.txt new file mode 100644 index 00000000..749daf31 --- /dev/null +++ b/examples/graphs/3d/widgetgallery/data/license.txt @@ -0,0 +1,77 @@ +License information regarding the data obtained from National Land Survey of +Finland http://www.maanmittauslaitos.fi/en +- topographic model from Elevation model 2 m (U4421B, U4421D, U4422A and + U4422C) 08/2014 +- map image extracted from Topographic map raster 1:50 000 (U442) 08/2014 + +National Land Survey open data licence - version 1.0 - 1 May 2012 + +1. General information + +The National Land Survey of Finland (hereinafter the Licensor), as the holder +of the immaterial rights to the data, has granted on the terms mentioned below +the right to use a copy (hereinafter data or dataset(s)) of the data (or a part +of it). + +The Licensee is a natural or legal person who makes use of the data covered by +this licence. The Licensee accepts the terms of this licence by receiving the +dataset(s) covered by the licence. + +This Licence agreement does not create a co-operation or business relationship +between the Licensee and the Licensor. + +2. Terms of the licence + +2.1. Right of use + +This licence grants a worldwide, free of charge and irrevocable parallel right +of use to open data. According to the terms of the licence, data received by +the Licensee can be freely: + - copied, distributed and published, + - modified and utilised commercially and non-commercially, + - inserted into other products and + - used as a part of a software application or service. + +2.2. Duties and responsibilities of the Licensee + +Through reasonable means suitable to the distribution medium or method which is +used in conjunction with a product containing data or a service utilising data +covered by this licence or while distributing data, the Licensee shall: + - mention the name of the Licensor, the name of the dataset(s) and the time + when the National Land Survey has delivered the dataset(s) (e.g.: contains + data from the National Land Survey of Finland Topographic Database 06/2012) + - provide a copy of this licence or a link to it, as well as + - require third parties to provide the same information when granting rights + to copies of dataset(s) or products and services containing such data and + - remove the name of the Licensor from the product or service, if required to + do so by the Licensor. + +The terms of this licence do not allow the Licensee to state in conjunction +with the use of dataset(s) that the Licensor supports or recommends such use. + +2.3. Duties and responsibilities of the Licensor + +The Licensor shall ensure that + - the Licensor has the right to grant rights to the dataset(s) in accordance + with this licence. + +The data has been licensed "as is" and the Licensor + - shall not be held responsible for any errors or omissions in the data, + disclaims any warranty for the validity or up to date status of the data and + shall be free from liability for direct or consequential damages arising + from the use of data provided by the Licensor, + - and is not obligated to ensure the continuous availability of the data, nor + to announce in advance the interruption or cessation of availability, and + the Licensor shall be free from liability for direct or consequential + damages arising from any such interruption or cessation. + +3. Jurisdiction + +Finnish law shall apply to this licence. + +4. Changes to this licence + +The Licensor may at any time change the terms of the licence or apply a +different licence to the data. The terms of this licence shall, however, still +apply to such data that has been received prior to the change of the terms of +the licence or the licence itself. diff --git a/examples/graphs/3d/widgetgallery/data/maptexture.jpg b/examples/graphs/3d/widgetgallery/data/maptexture.jpg new file mode 100644 index 00000000..ae5d66eb Binary files /dev/null and b/examples/graphs/3d/widgetgallery/data/maptexture.jpg differ diff --git a/examples/graphs/3d/widgetgallery/data/narrowarrow.mesh b/examples/graphs/3d/widgetgallery/data/narrowarrow.mesh new file mode 100644 index 00000000..288867b1 Binary files /dev/null and b/examples/graphs/3d/widgetgallery/data/narrowarrow.mesh differ diff --git a/examples/graphs/3d/widgetgallery/data/oilrig.mesh b/examples/graphs/3d/widgetgallery/data/oilrig.mesh new file mode 100644 index 00000000..4a7baedd Binary files /dev/null and b/examples/graphs/3d/widgetgallery/data/oilrig.mesh differ diff --git a/examples/graphs/3d/widgetgallery/data/pipe.mesh b/examples/graphs/3d/widgetgallery/data/pipe.mesh new file mode 100644 index 00000000..984b6d44 Binary files /dev/null and b/examples/graphs/3d/widgetgallery/data/pipe.mesh differ diff --git a/examples/graphs/3d/widgetgallery/data/raindata.txt b/examples/graphs/3d/widgetgallery/data/raindata.txt new file mode 100644 index 00000000..d9558921 --- /dev/null +++ b/examples/graphs/3d/widgetgallery/data/raindata.txt @@ -0,0 +1,158 @@ +# Rainfall per month from 2010 to 2022 in Northern Finland (Oulu) +# Format: year, month, rainfall +2010,1, 0, +2010,2, 3.4, +2010,3, 52, +2010,4, 33.8, +2010,5, 45.6, +2010,6, 43.8, +2010,7, 104.6, +2010,8, 105.4, +2010,9, 107.2, +2010,10,38.6, +2010,11,17.8, +2010,12,0, +2011,1, 8.2, +2011,2, 1.6, +2011,3, 27.4, +2011,4, 15.8, +2011,5, 57.6, +2011,6, 85.2, +2011,7, 127, +2011,8, 72.2, +2011,9, 82.2, +2011,10,62.4, +2011,11,31.6, +2011,12,53.8, +2012,1, 0, +2012,2, 5, +2012,3, 32.4, +2012,4, 57.6, +2012,5, 71.4, +2012,6, 60.8, +2012,7, 109, +2012,8, 43.6, +2012,9, 79.4, +2012,10,117.2, +2012,11,59, +2012,12,0.2, +2013,1, 28, +2013,2, 19, +2013,3, 0, +2013,4, 37.6, +2013,5, 44.2, +2013,6, 104.8, +2013,7, 84.2, +2013,8, 57.2, +2013,9, 37.2, +2013,10,64.6, +2013,11,77.8, +2013,12,92.8, +2014,1, 23.8, +2014,2, 23.6, +2014,3, 15.4, +2014,4, 13.2, +2014,5, 36.4, +2014,6, 26.4, +2014,7, 95.8, +2014,8, 81.8, +2014,9, 13.8, +2014,10,94.6, +2014,11,44.6, +2014,12,31, +2015,1, 37.4, +2015,2, 21, +2015,3, 42, +2015,4, 8.8, +2015,5, 82.4, +2015,6, 150, +2015,7, 56.8, +2015,8, 67.2, +2015,9, 131.2, +2015,10,38.4, +2015,11,83.4, +2015,12,47.8, +2016,1, 12.4, +2016,2, 34.8, +2016,3, 29, +2016,4, 40.4, +2016,5, 32.4, +2016,6, 80.2, +2016,7, 102.6, +2016,8, 95.6, +2016,9, 40.2, +2016,10,7.8, +2016,11,39.6, +2016,12,8.8, +2017,1, 9.4, +2017,2, 6.6, +2017,3, 29, +2017,4, 46.2, +2017,5, 43.2, +2017,6, 25.2, +2017,7, 72.4, +2017,8, 58.8, +2017,9, 68.8, +2017,10,45.8, +2017,11,36.8, +2017,12,29.6, +2018,1, 19.8, +2018,2, 0.8, +2018,3, 4, +2018,4, 23.2, +2018,5, 13.2, +2018,6, 62.8, +2018,7, 33, +2018,8, 96.6, +2018,9, 72.6, +2018,10,48.8, +2018,11,31.8, +2018,12,12.8, +2019,1, 0.2, +2019,2, 24.8, +2019,3, 32, +2019,4, 8.8, +2019,5, 71.4, +2019,6, 65.8, +2019,7, 17.6, +2019,8, 90, +2019,9, 50, +2019,10,77, +2019,11,27, +2019,12,43.2, +2020,1, 28.8, +2020,2, 45, +2020,3, 18.6, +2020,4, 13, +2020,5, 30.8, +2020,6, 21.4, +2020,7, 163.6, +2020,8, 12, +2020,9, 102.4, +2020,10,133.2, +2020,11,69.8, +2020,12,40.6, +2021,1, 0.4, +2021,2, 21.6, +2021,3, 24, +2021,4, 51.4, +2021,5, 76.4, +2021,6, 29.2, +2021,7, 36.4, +2021,8, 116, +2021,9, 72.4, +2021,10,93.4, +2021,11,21, +2021,12,10.2, +2022,1, 8.6, +2022,2, 6.6, +2022,3, 5.2, +2022,4, 15.2, +2022,5, 37.6, +2022,6, 45, +2022,7, 67.4, +2022,8, 161.6, +2022,9, 22.8, +2022,10,75.2, +2022,11,21.8, +2022,12,0.2 diff --git a/examples/graphs/3d/widgetgallery/data/refinery.mesh b/examples/graphs/3d/widgetgallery/data/refinery.mesh new file mode 100644 index 00000000..a7e24935 Binary files /dev/null and b/examples/graphs/3d/widgetgallery/data/refinery.mesh differ diff --git a/examples/graphs/3d/widgetgallery/data/topography.png b/examples/graphs/3d/widgetgallery/data/topography.png new file mode 100644 index 00000000..9349cdb3 Binary files /dev/null and b/examples/graphs/3d/widgetgallery/data/topography.png differ diff --git a/examples/graphs/3d/widgetgallery/doc/widgetgallery.rst b/examples/graphs/3d/widgetgallery/doc/widgetgallery.rst new file mode 100644 index 00000000..1470001d --- /dev/null +++ b/examples/graphs/3d/widgetgallery/doc/widgetgallery.rst @@ -0,0 +1,11 @@ +Widget Gallery +============== + + +Widget Gallery demonstrates all three graph types and some of their special +features. The graphs have their own tabs in the application. + + +.. image:: widgetgallery.webp + :width: 400 + :alt: Widget Screenshot diff --git a/examples/graphs/3d/widgetgallery/doc/widgetgallery.webp b/examples/graphs/3d/widgetgallery/doc/widgetgallery.webp new file mode 100644 index 00000000..eb576726 Binary files /dev/null and b/examples/graphs/3d/widgetgallery/doc/widgetgallery.webp differ diff --git a/examples/graphs/3d/widgetgallery/graphmodifier.py b/examples/graphs/3d/widgetgallery/graphmodifier.py new file mode 100644 index 00000000..2eaafa79 --- /dev/null +++ b/examples/graphs/3d/widgetgallery/graphmodifier.py @@ -0,0 +1,391 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + + +from math import atan, degrees +import numpy as np + +from PySide6.QtCore import QObject, QPropertyAnimation, Signal, Slot +from PySide6.QtGui import QFont, QVector3D +from PySide6.QtGraphs import (QAbstract3DGraph, QAbstract3DSeries, + QBarDataItem, QBar3DSeries, QCategory3DAxis, + QValue3DAxis, Q3DTheme) + +from rainfalldata import RainfallData + +# Set up data +TEMP_OULU = np.array([ + [-7.4, -2.4, 0.0, 3.0, 8.2, 11.6, 14.7, 15.4, 11.4, 4.2, 2.1, -2.3], # 2015 + [-13.4, -3.9, -1.8, 3.1, 10.6, 13.7, 17.8, 13.6, 10.7, 3.5, -3.1, -4.2], # 2016 + [-5.7, -6.7, -3.0, -0.1, 4.7, 12.4, 16.1, 14.1, 9.4, 3.0, -0.3, -3.2], # 2017 + [-6.4, -11.9, -7.4, 1.9, 11.4, 12.4, 21.5, 16.1, 11.0, 4.4, 2.1, -4.1], # 2018 + [-11.7, -6.1, -2.4, 3.9, 7.2, 14.5, 15.6, 14.4, 8.5, 2.0, -3.0, -1.5], # 2019 + [-2.1, -3.4, -1.8, 0.6, 7.0, 17.1, 15.6, 15.4, 11.1, 5.6, 1.9, -1.7], # 2020 + [-9.6, -11.6, -3.2, 2.4, 7.8, 17.3, 19.4, 14.2, 8.0, 5.2, -2.2, -8.6], # 2021 + [-7.3, -6.4, -1.8, 1.3, 8.1, 15.5, 17.6, 17.6, 9.1, 5.4, -1.5, -4.4]], # 2022 + np.float64) + + +TEMP_HELSINKI = np.array([ + [-2.0, -0.1, 1.8, 5.1, 9.7, 13.7, 16.3, 17.3, 12.7, 5.4, 4.6, 2.1], # 2015 + [-10.3, -0.6, 0.0, 4.9, 14.3, 15.7, 17.7, 16.0, 12.7, 4.6, -1.0, -0.9], # 2016 + [-2.9, -3.3, 0.7, 2.3, 9.9, 13.8, 16.1, 15.9, 11.4, 5.0, 2.7, 0.7], # 2017 + [-2.2, -8.4, -4.7, 5.0, 15.3, 15.8, 21.2, 18.2, 13.3, 6.7, 2.8, -2.0], # 2018 + [-6.2, -0.5, -0.3, 6.8, 10.6, 17.9, 17.5, 16.8, 11.3, 5.2, 1.8, 1.4], # 2019 + [1.9, 0.5, 1.7, 4.5, 9.5, 18.4, 16.5, 16.8, 13.0, 8.2, 4.4, 0.9], # 2020 + [-4.7, -8.1, -0.9, 4.5, 10.4, 19.2, 20.9, 15.4, 9.5, 8.0, 1.5, -6.7], # 2021 + [-3.3, -2.2, -0.2, 3.3, 9.6, 16.9, 18.1, 18.9, 9.2, 7.6, 2.3, -3.4]], # 2022 + np.float64) + + +class GraphModifier(QObject): + + shadowQualityChanged = Signal(int) + backgroundEnabledChanged = Signal(bool) + gridEnabledChanged = Signal(bool) + fontChanged = Signal(QFont) + fontSizeChanged = Signal(int) + + def __init__(self, bargraph, parent): + super().__init__(parent) + self._graph = bargraph + self._temperatureAxis = QValue3DAxis() + self._yearAxis = QCategory3DAxis() + self._monthAxis = QCategory3DAxis() + self._primarySeries = QBar3DSeries() + self._secondarySeries = QBar3DSeries() + self._celsiusString = "°C" + + self._xRotation = float(0) + self._yRotation = float(0) + self._fontSize = 30 + self._segments = 4 + self._subSegments = 3 + self._minval = float(-20) + self._maxval = float(20) + self._barMesh = QAbstract3DSeries.Mesh.BevelBar + self._smooth = False + self._animationCameraX = QPropertyAnimation() + self._animationCameraY = QPropertyAnimation() + self._animationCameraZoom = QPropertyAnimation() + self._animationCameraTarget = QPropertyAnimation() + self._defaultAngleX = float(0) + self._defaultAngleY = float(0) + self._defaultZoom = float(0) + self._defaultTarget = [] + self._customData = None + + self._graph.setShadowQuality(QAbstract3DGraph.ShadowQuality.SoftMedium) + theme = self._graph.activeTheme() + theme.setBackgroundEnabled(False) + theme.setFont(QFont("Times New Roman", self._fontSize)) + theme.setLabelBackgroundEnabled(True) + self._graph.setMultiSeriesUniform(True) + + self._months = ["January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", + "December"] + self._years = ["2015", "2016", "2017", "2018", "2019", "2020", + "2021", "2022"] + + self._temperatureAxis.setTitle("Average temperature") + self._temperatureAxis.setSegmentCount(self._segments) + self._temperatureAxis.setSubSegmentCount(self._subSegments) + self._temperatureAxis.setRange(self._minval, self._maxval) + self._temperatureAxis.setLabelFormat("%.1f " + self._celsiusString) + self._temperatureAxis.setLabelAutoRotation(30.0) + self._temperatureAxis.setTitleVisible(True) + + self._yearAxis.setTitle("Year") + self._yearAxis.setLabelAutoRotation(30.0) + self._yearAxis.setTitleVisible(True) + self._monthAxis.setTitle("Month") + self._monthAxis.setLabelAutoRotation(30.0) + self._monthAxis.setTitleVisible(True) + + self._graph.setValueAxis(self._temperatureAxis) + self._graph.setRowAxis(self._yearAxis) + self._graph.setColumnAxis(self._monthAxis) + + format = "Oulu - @colLabel @rowLabel: @valueLabel" + self._primarySeries.setItemLabelFormat(format) + self._primarySeries.setMesh(QAbstract3DSeries.Mesh.BevelBar) + self._primarySeries.setMeshSmooth(False) + + format = "Helsinki - @colLabel @rowLabel: @valueLabel" + self._secondarySeries.setItemLabelFormat(format) + self._secondarySeries.setMesh(QAbstract3DSeries.Mesh.BevelBar) + self._secondarySeries.setMeshSmooth(False) + self._secondarySeries.setVisible(False) + + self._graph.addSeries(self._primarySeries) + self._graph.addSeries(self._secondarySeries) + + self.changePresetCamera() + + self.resetTemperatureData() + + # Set up property animations for zooming to the selected bar + self._defaultAngleX = self._graph.cameraXRotation() + self._defaultAngleY = self._graph.cameraYRotation() + self._defaultZoom = self._graph.cameraZoomLevel() + self._defaultTarget = self._graph.cameraTargetPosition() + + self._animationCameraX.setTargetObject(self._graph) + self._animationCameraY.setTargetObject(self._graph) + self._animationCameraZoom.setTargetObject(self._graph) + self._animationCameraTarget.setTargetObject(self._graph) + + self._animationCameraX.setPropertyName(b"cameraXRotation") + self._animationCameraY.setPropertyName(b"cameraYRotation") + self._animationCameraZoom.setPropertyName(b"cameraZoomLevel") + self._animationCameraTarget.setPropertyName(b"cameraTargetPosition") + + duration = 1700 + self._animationCameraX.setDuration(duration) + self._animationCameraY.setDuration(duration) + self._animationCameraZoom.setDuration(duration) + self._animationCameraTarget.setDuration(duration) + + # The zoom always first zooms out above the graph and then zooms in + zoomOutFraction = 0.3 + self._animationCameraX.setKeyValueAt(zoomOutFraction, 0.0) + self._animationCameraY.setKeyValueAt(zoomOutFraction, 90.0) + self._animationCameraZoom.setKeyValueAt(zoomOutFraction, 50.0) + self._animationCameraTarget.setKeyValueAt(zoomOutFraction, + QVector3D(0, 0, 0)) + self._customData = RainfallData() + + def resetTemperatureData(self): + # Create data arrays + dataSet = [] + dataSet2 = [] + + for year in range(0, len(self._years)): + # Create a data row + dataRow = [] + dataRow2 = [] + for month in range(0, len(self._months)): + # Add data to the row + item = QBarDataItem() + item.setValue(TEMP_OULU[year][month]) + dataRow.append(item) + item = QBarDataItem() + item.setValue(TEMP_HELSINKI[year][month]) + dataRow2.append(item) + + # Add the row to the set + dataSet.append(dataRow) + dataSet2.append(dataRow2) + + # Add data to the data proxy (the data proxy assumes ownership of it) + self._primarySeries.dataProxy().resetArray(dataSet, self._years, self._months) + self._secondarySeries.dataProxy().resetArray(dataSet2, self._years, self._months) + + @Slot(int) + def changeRange(self, range): + if range >= len(self._years): + self._yearAxis.setRange(0, len(self._years) - 1) + else: + self._yearAxis.setRange(range, range) + + @Slot(int) + def changeStyle(self, style): + comboBox = self.sender() + if comboBox: + self._barMesh = comboBox.itemData(style) + self._primarySeries.setMesh(self._barMesh) + self._secondarySeries.setMesh(self._barMesh) + self._customData.customSeries().setMesh(self._barMesh) + + def changePresetCamera(self): + self._animationCameraX.stop() + self._animationCameraY.stop() + self._animationCameraZoom.stop() + self._animationCameraTarget.stop() + + # Restore camera target in case animation has changed it + self._graph.setCameraTargetPosition(QVector3D(0.0, 0.0, 0.0)) + + self._preset = QAbstract3DGraph.CameraPreset.Front.value + + self._graph.setCameraPreset(QAbstract3DGraph.CameraPreset(self._preset)) + + self._preset += 1 + if self._preset > QAbstract3DGraph.CameraPreset.DirectlyBelow.value: + self._preset = QAbstract3DGraph.CameraPreset.FrontLow.value + + @Slot(int) + def changeTheme(self, theme): + currentTheme = self._graph.activeTheme() + currentTheme.setType(Q3DTheme.Theme(theme)) + self.backgroundEnabledChanged.emit(currentTheme.isBackgroundEnabled()) + self.gridEnabledChanged.emit(currentTheme.isGridEnabled()) + self.fontChanged.emit(currentTheme.font()) + self.fontSizeChanged.emit(currentTheme.font().pointSize()) + + def changeLabelBackground(self): + theme = self._graph.activeTheme() + theme.setLabelBackgroundEnabled(not theme.isLabelBackgroundEnabled()) + + @Slot(int) + def changeSelectionMode(self, selectionMode): + comboBox = self.sender() + if comboBox: + flags = comboBox.itemData(selectionMode) + self._graph.setSelectionMode(QAbstract3DGraph.SelectionFlags(flags)) + + def changeFont(self, font): + newFont = font + self._graph.activeTheme().setFont(newFont) + + def changeFontSize(self, fontsize): + self._fontSize = fontsize + font = self._graph.activeTheme().font() + font.setPointSize(self._fontSize) + self._graph.activeTheme().setFont(font) + + @Slot(QAbstract3DGraph.ShadowQuality) + def shadowQualityUpdatedByVisual(self, sq): + # Updates the UI component to show correct shadow quality + self.shadowQualityChanged.emit(sq.value) + + @Slot(int) + def changeLabelRotation(self, rotation): + self._temperatureAxis.setLabelAutoRotation(float(rotation)) + self._monthAxis.setLabelAutoRotation(float(rotation)) + self._yearAxis.setLabelAutoRotation(float(rotation)) + + @Slot(bool) + def setAxisTitleVisibility(self, enabled): + self._temperatureAxis.setTitleVisible(enabled) + self._monthAxis.setTitleVisible(enabled) + self._yearAxis.setTitleVisible(enabled) + + @Slot(bool) + def setAxisTitleFixed(self, enabled): + self._temperatureAxis.setTitleFixed(enabled) + self._monthAxis.setTitleFixed(enabled) + self._yearAxis.setTitleFixed(enabled) + + @Slot() + def zoomToSelectedBar(self): + self._animationCameraX.stop() + self._animationCameraY.stop() + self._animationCameraZoom.stop() + self._animationCameraTarget.stop() + + currentX = self._graph.cameraXRotation() + currentY = self._graph.cameraYRotation() + currentZoom = self._graph.cameraZoomLevel() + currentTarget = self._graph.cameraTargetPosition() + + self._animationCameraX.setStartValue(currentX) + self._animationCameraY.setStartValue(currentY) + self._animationCameraZoom.setStartValue(currentZoom) + self._animationCameraTarget.setStartValue(currentTarget) + + selectedBar = (self._graph.selectedSeries().selectedBar() + if self._graph.selectedSeries() + else QBar3DSeries.invalidSelectionPosition()) + + if selectedBar != QBar3DSeries.invalidSelectionPosition(): + # Normalize selected bar position within axis range to determine + # target coordinates + endTarget = QVector3D() + xMin = self._graph.columnAxis().min() + xRange = self._graph.columnAxis().max() - xMin + zMin = self._graph.rowAxis().min() + zRange = self._graph.rowAxis().max() - zMin + endTarget.setX((selectedBar.y() - xMin) / xRange * 2.0 - 1.0) + endTarget.setZ((selectedBar.x() - zMin) / zRange * 2.0 - 1.0) + + # Rotate the camera so that it always points approximately to the + # graph center + endAngleX = 90.0 - degrees(atan(float(endTarget.z() / endTarget.x()))) + if endTarget.x() > 0.0: + endAngleX -= 180.0 + proxy = self._graph.selectedSeries().dataProxy() + barValue = proxy.itemAt(selectedBar.x(), selectedBar.y()).value() + endAngleY = 30.0 if barValue >= 0.0 else -30.0 + if self._graph.valueAxis().reversed(): + endAngleY *= -1.0 + + self._animationCameraX.setEndValue(float(endAngleX)) + self._animationCameraY.setEndValue(endAngleY) + self._animationCameraZoom.setEndValue(250) + self._animationCameraTarget.setEndValue(endTarget) + else: + # No selected bar, so return to the default view + self._animationCameraX.setEndValue(self._defaultAngleX) + self._animationCameraY.setEndValue(self._defaultAngleY) + self._animationCameraZoom.setEndValue(self._defaultZoom) + self._animationCameraTarget.setEndValue(self._defaultTarget) + + self._animationCameraX.start() + self._animationCameraY.start() + self._animationCameraZoom.start() + self._animationCameraTarget.start() + + @Slot(bool) + def setDataModeToWeather(self, enabled): + if enabled: + self.changeDataMode(False) + + @Slot(bool) + def setDataModeToCustom(self, enabled): + if enabled: + self.changeDataMode(True) + + def changeShadowQuality(self, quality): + sq = QAbstract3DGraph.ShadowQuality(quality) + self._graph.setShadowQuality(sq) + self.shadowQualityChanged.emit(quality) + + def rotateX(self, rotation): + self._xRotation = rotation + camera = self._graph.scene().activeCamera() + camera.setCameraPosition(self._xRotation, self._yRotation) + + def rotateY(self, rotation): + self._yRotation = rotation + camera = self._graph.scene().activeCamera() + camera.setCameraPosition(self._xRotation, self._yRotation) + + def setBackgroundEnabled(self, enabled): + self._graph.activeTheme().setBackgroundEnabled(bool(enabled)) + + def setGridEnabled(self, enabled): + self._graph.activeTheme().setGridEnabled(bool(enabled)) + + def setSmoothBars(self, smooth): + self._smooth = bool(smooth) + self._primarySeries.setMeshSmooth(self._smooth) + self._secondarySeries.setMeshSmooth(self._smooth) + self._customData.customSeries().setMeshSmooth(self._smooth) + + def setSeriesVisibility(self, enabled): + self._secondarySeries.setVisible(bool(enabled)) + + def setReverseValueAxis(self, enabled): + self._graph.valueAxis().setReversed(enabled) + + def setReflection(self, enabled): + self._graph.setReflection(enabled) + + def changeDataMode(self, customData): + # Change between weather data and data from custom proxy + if customData: + self._graph.removeSeries(self._primarySeries) + self._graph.removeSeries(self._secondarySeries) + self._graph.addSeries(self._customData.customSeries()) + self._graph.setValueAxis(self._customData.valueAxis()) + self._graph.setRowAxis(self._customData.rowAxis()) + self._graph.setColumnAxis(self._customData.colAxis()) + else: + self._graph.removeSeries(self._customData.customSeries()) + self._graph.addSeries(self._primarySeries) + self._graph.addSeries(self._secondarySeries) + self._graph.setValueAxis(self._temperatureAxis) + self._graph.setRowAxis(self._yearAxis) + self._graph.setColumnAxis(self._monthAxis) diff --git a/examples/graphs/3d/widgetgallery/highlightseries.py b/examples/graphs/3d/widgetgallery/highlightseries.py new file mode 100644 index 00000000..8c7b9163 --- /dev/null +++ b/examples/graphs/3d/widgetgallery/highlightseries.py @@ -0,0 +1,94 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from PySide6.QtCore import QPoint, Qt, Slot +from PySide6.QtGui import QLinearGradient, QVector3D +from PySide6.QtGraphs import (QSurface3DSeries, QSurfaceDataItem, Q3DTheme) + + +DARK_RED_POS = 1.0 +RED_POS = 0.8 +YELLOW_POS = 0.6 +GREEN_POS = 0.4 +DARK_GREEN_POS = 0.2 + + +class HighlightSeries(QSurface3DSeries): + + def __init__(self): + super().__init__() + self._width = 100 + self._height = 100 + self._srcWidth = 0 + self._srcHeight = 0 + self._position = {} + self._topographicSeries = None + self._minHeight = 0.0 + self.setDrawMode(QSurface3DSeries.DrawSurface) + self.setFlatShadingEnabled(True) + self.setVisible(False) + + def setTopographicSeries(self, series): + self._topographicSeries = series + array = self._topographicSeries.dataProxy().array() + self._srcWidth = len(array[0]) + self._srcHeight = len(array) + self._topographicSeries.selectedPointChanged.connect(self.handlePositionChange) + + def setMinHeight(self, height): + self. m_minHeight = height + + @Slot(QPoint) + def handlePositionChange(self, position): + self._position = position + + if position == self.invalidSelectionPosition(): + self.setVisible(False) + return + + halfWidth = self._width / 2 + halfHeight = self._height / 2 + + startX = position.y() - halfWidth + if startX < 0: + startX = 0 + endX = position.y() + halfWidth + if endX > (self._srcWidth - 1): + endX = self._srcWidth - 1 + startZ = position.x() - halfHeight + if startZ < 0: + startZ = 0 + endZ = position.x() + halfHeight + if endZ > (self._srcHeight - 1): + endZ = self._srcHeight - 1 + + srcProxy = self._topographicSeries.dataProxy() + srcArray = srcProxy.array() + + dataArray = [] + for i in range(int(startZ), int(endZ)): + newRow = [] + srcRow = srcArray[i] + for j in range(startX, endX): + pos = srcRow.at(j).position() + pos.setY(pos.y() + 0.1) + item = QSurfaceDataItem(QVector3D(pos)) + newRow.append(item) + dataArray.append(newRow) + self.dataProxy().resetArray(dataArray) + self.setVisible(True) + + @Slot(float) + def handleGradientChange(self, value): + ratio = self._minHeight / value + + gr = QLinearGradient() + gr.setColorAt(0.0, Qt.black) + gr.setColorAt(DARK_GREEN_POS * ratio, Qt.darkGreen) + gr.setColorAt(GREEN_POS * ratio, Qt.green) + gr.setColorAt(YELLOW_POS * ratio, Qt.yellow) + gr.setColorAt(RED_POS * ratio, Qt.red) + gr.setColorAt(DARK_RED_POS * ratio, Qt.darkRed) + + self.setBaseGradient(gr) + self.setColorStyle(Q3DTheme.ColorStyle.RangeGradient) diff --git a/examples/graphs/3d/widgetgallery/main.py b/examples/graphs/3d/widgetgallery/main.py new file mode 100644 index 00000000..7bb2238a --- /dev/null +++ b/examples/graphs/3d/widgetgallery/main.py @@ -0,0 +1,41 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +"""PySide6 port of the Qt Graphs widgetgallery example from Qt v6.x""" + +import sys + +from PySide6.QtCore import QSize +from PySide6.QtWidgets import QApplication, QTabWidget + +from bargraph import BarGraph +from scattergraph import ScatterGraph +from surfacegraph import SurfaceGraph + + +if __name__ == "__main__": + app = QApplication(sys.argv) + + # Create a tab widget for creating own tabs for Q3DBars, Q3DScatter, and Q3DSurface + tabWidget = QTabWidget() + tabWidget.setWindowTitle("Widget Gallery") + + screen_size = tabWidget.screen().size() + minimum_graph_size = QSize(screen_size.width() / 2, screen_size.height() / 1.75) + + # Create bar graph + bars = BarGraph(minimum_graph_size, screen_size) + # Create scatter graph + scatter = ScatterGraph(minimum_graph_size, screen_size) + # Create surface graph + surface = SurfaceGraph(minimum_graph_size, screen_size) + + # Add bars widget + tabWidget.addTab(bars.barsWidget(), "Bar Graph") + # Add scatter widget + tabWidget.addTab(scatter.scatterWidget(), "Scatter Graph") + # Add surface widget + tabWidget.addTab(surface.surfaceWidget(), "Surface Graph") + + tabWidget.show() + sys.exit(app.exec()) diff --git a/examples/graphs/3d/widgetgallery/rainfalldata.py b/examples/graphs/3d/widgetgallery/rainfalldata.py new file mode 100644 index 00000000..d74f45a8 --- /dev/null +++ b/examples/graphs/3d/widgetgallery/rainfalldata.py @@ -0,0 +1,125 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import sys + +from pathlib import Path + +from PySide6.QtCore import QFile, QIODevice, QObject +from PySide6.QtGraphs import (QBar3DSeries, QCategory3DAxis, QValue3DAxis) + +from variantbardataproxy import VariantBarDataProxy +from variantbardatamapping import VariantBarDataMapping +from variantdataset import VariantDataSet + + +MONTHS = ["January", "February", "March", "April", + "May", "June", "July", "August", "September", "October", + "November", "December"] + + +class RainfallData(QObject): + + def __init__(self): + super().__init__() + self._columnCount = 0 + self._rowCount = 0 + self._years = [] + self._numericMonths = [] + self._proxy = VariantBarDataProxy() + self._mapping = None + self._dataSet = None + self._series = QBar3DSeries() + self._valueAxis = QValue3DAxis() + self._rowAxis = QCategory3DAxis() + self._colAxis = QCategory3DAxis() + + # In data file the months are in numeric format, so create custom list + for i in range(1, 13): + self._numericMonths.append(str(i)) + + self._columnCount = len(self._numericMonths) + + self.updateYearsList(2010, 2022) + + # Create proxy and series + self._proxy = VariantBarDataProxy() + self._series = QBar3DSeries(self._proxy) + + self._series.setItemLabelFormat("%.1f mm") + + # Create the axes + self._rowAxis = QCategory3DAxis(self) + self._colAxis = QCategory3DAxis(self) + self._valueAxis = QValue3DAxis(self) + self._rowAxis.setAutoAdjustRange(True) + self._colAxis.setAutoAdjustRange(True) + self._valueAxis.setAutoAdjustRange(True) + + # Set axis labels and titles + self._rowAxis.setTitle("Year") + self._colAxis.setTitle("Month") + self._valueAxis.setTitle("rainfall (mm)") + self._valueAxis.setSegmentCount(5) + self._rowAxis.setLabels(self._years) + self._colAxis.setLabels(MONTHS) + self._rowAxis.setTitleVisible(True) + self._colAxis.setTitleVisible(True) + self._valueAxis.setTitleVisible(True) + + self.addDataSet() + + def customSeries(self): + return self._series + + def valueAxis(self): + return self._valueAxis + + def rowAxis(self): + return self._rowAxis + + def colAxis(self): + return self._colAxis + + def updateYearsList(self, start, end): + self._years.clear() + for i in range(start, end + 1): + self._years.append(str(i)) + self._rowCount = len(self._years) + + def addDataSet(self): + # Create a new variant data set and data item list + self._dataSet = VariantDataSet() + itemList = [] + + # Read data from a data file into the data item list + file_path = Path(__file__).resolve().parent / "data" / "raindata.txt" + dataFile = QFile(file_path) + if dataFile.open(QIODevice.ReadOnly | QIODevice.Text): + data = dataFile.readAll().data().decode("utf8") + for line in data.split("\n"): + if line and not line.startswith("#"): # Ignore comments + tokens = line.split(",") + # Each line has three data items: Year, month, and + # rainfall value + if len(tokens) >= 3: + # Store year and month as strings, and rainfall value + # as double into a variant data item and add the item to + # the item list. + newItem = [] + newItem.append(tokens[0].strip()) + newItem.append(tokens[1].strip()) + newItem.append(float(tokens[2].strip())) + itemList.append(newItem) + else: + print("Unable to open data file:", dataFile.fileName(), + file=sys.stderr) + + # Add items to the data set and set it to the proxy + self._dataSet.addItems(itemList) + self._proxy.setDataSet(self._dataSet) + + # Create new mapping for the data and set it to the proxy + self._mapping = VariantBarDataMapping(0, 1, 2, + self._years, self._numericMonths) + self._proxy.setMapping(self._mapping) diff --git a/examples/graphs/3d/widgetgallery/scatterdatamodifier.py b/examples/graphs/3d/widgetgallery/scatterdatamodifier.py new file mode 100644 index 00000000..15064b41 --- /dev/null +++ b/examples/graphs/3d/widgetgallery/scatterdatamodifier.py @@ -0,0 +1,149 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from math import cos, degrees, sqrt + +from PySide6.QtCore import QObject, Signal, Slot, Qt +from PySide6.QtGui import QVector3D +from PySide6.QtGraphs import (QAbstract3DGraph, QAbstract3DSeries, + QScatterDataItem, QScatterDataProxy, + QScatter3DSeries, Q3DTheme) + +from axesinputhandler import AxesInputHandler + + +NUMBER_OF_ITEMS = 10000 +CURVE_DIVIDER = 7.5 +LOWER_NUMBER_OF_ITEMS = 900 +LOWER_CURVE_DIVIDER = 0.75 + + +class ScatterDataModifier(QObject): + + backgroundEnabledChanged = Signal(bool) + gridEnabledChanged = Signal(bool) + shadowQualityChanged = Signal(int) + + def __init__(self, scatter, parent): + super().__init__(parent) + + self._graph = scatter + + self._style = QAbstract3DSeries.Mesh.Sphere + self._smooth = True + self._inputHandler = AxesInputHandler(scatter) + self._autoAdjust = True + self._itemCount = LOWER_NUMBER_OF_ITEMS + self._CURVE_DIVIDER = LOWER_CURVE_DIVIDER + self._inputHandler = AxesInputHandler(scatter) + + self._graph.activeTheme().setType(Q3DTheme.Theme.StoneMoss) + self._graph.setShadowQuality(QAbstract3DGraph.ShadowQuality.SoftHigh) + self._graph.setCameraPreset(QAbstract3DGraph.CameraPreset.Front) + self._graph.setCameraZoomLevel(80.0) + + self._proxy = QScatterDataProxy() + self._series = QScatter3DSeries(self._proxy) + self._series.setItemLabelFormat("@xTitle: @xLabel @yTitle: @yLabel @zTitle: @zLabel") + self._series.setMeshSmooth(self._smooth) + self._graph.addSeries(self._series) + self._preset = QAbstract3DGraph.CameraPreset.FrontLow.value + + # Give ownership of the handler to the graph and make it the active + # handler + self._graph.setActiveInputHandler(self._inputHandler) + + # Give our axes to the input handler + self._inputHandler.setAxes(self._graph.axisX(), self._graph.axisZ(), + self._graph.axisY()) + + self.addData() + + def addData(self): + # Configure the axes according to the data + self._graph.axisX().setTitle("X") + self._graph.axisY().setTitle("Y") + self._graph.axisZ().setTitle("Z") + + dataArray = [] + limit = int(sqrt(self._itemCount) / 2.0) + for i in range(-limit, limit): + for j in range(-limit, limit): + x = float(i) + 0.5 + y = cos(degrees(float(i * j) / self._CURVE_DIVIDER)) + z = float(j) + 0.5 + dataArray.append(QScatterDataItem(QVector3D(x, y, z))) + + self._graph.seriesList()[0].dataProxy().resetArray(dataArray) + + @Slot(int) + def changeStyle(self, style): + comboBox = self.sender() + if comboBox: + self._style = comboBox.itemData(style) + if self._graph.seriesList(): + self._graph.seriesList()[0].setMesh(self._style) + + @Slot(int) + def setSmoothDots(self, smooth): + self._smooth = smooth == Qt.Checked.value + series = self._graph.seriesList()[0] + series.setMeshSmooth(self._smooth) + + @Slot(int) + def changeTheme(self, theme): + currentTheme = self._graph.activeTheme() + currentTheme.setType(Q3DTheme.Theme(theme)) + self.backgroundEnabledChanged.emit(currentTheme.isBackgroundEnabled()) + self.gridEnabledChanged.emit(currentTheme.isGridEnabled()) + + @Slot() + def changePresetCamera(self): + camera = self._graph.scene().activeCamera() + camera.setCameraPreset(QAbstract3DGraph.CameraPreset(self._preset)) + + self._preset += 1 + if self._preset > QAbstract3DGraph.CameraPreset.DirectlyBelow.value: + self._preset = QAbstract3DGraph.CameraPreset.FrontLow.value + + @Slot(QAbstract3DGraph.ShadowQuality) + def shadowQualityUpdatedByVisual(self, sq): + self.shadowQualityChanged.emit(sq.value) + + @Slot(int) + def changeShadowQuality(self, quality): + sq = QAbstract3DGraph.ShadowQuality(quality) + self._graph.setShadowQuality(sq) + + @Slot(int) + def setBackgroundEnabled(self, enabled): + self._graph.activeTheme().setBackgroundEnabled(enabled == Qt.Checked.value) + + @Slot(int) + def setGridEnabled(self, enabled): + self._graph.activeTheme().setGridEnabled(enabled == Qt.Checked.value) + + @Slot() + def toggleItemCount(self): + if self._itemCount == NUMBER_OF_ITEMS: + self._itemCount = LOWER_NUMBER_OF_ITEMS + self._CURVE_DIVIDER = LOWER_CURVE_DIVIDER + else: + self._itemCount = NUMBER_OF_ITEMS + self._CURVE_DIVIDER = CURVE_DIVIDER + + self._graph.seriesList()[0].dataProxy().resetArray([]) + self.addData() + + @Slot() + def toggleRanges(self): + if not self._autoAdjust: + self._graph.axisX().setAutoAdjustRange(True) + self._graph.axisZ().setAutoAdjustRange(True) + self._inputHandler.setDragSpeedModifier(1.5) + self._autoAdjust = True + else: + self._graph.axisX().setRange(-10.0, 10.0) + self._graph.axisZ().setRange(-10.0, 10.0) + self._inputHandler.setDragSpeedModifier(15.0) + self._autoAdjust = False diff --git a/examples/graphs/3d/widgetgallery/scattergraph.py b/examples/graphs/3d/widgetgallery/scattergraph.py new file mode 100644 index 00000000..79e8933e --- /dev/null +++ b/examples/graphs/3d/widgetgallery/scattergraph.py @@ -0,0 +1,121 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from PySide6.QtCore import QObject, QSize, Qt +from PySide6.QtWidgets import (QCheckBox, QComboBox, QCommandLinkButton, + QLabel, QHBoxLayout, QSizePolicy, + QVBoxLayout, QWidget, ) +from PySide6.QtQuickWidgets import QQuickWidget +from PySide6.QtGraphs import (QAbstract3DSeries, Q3DScatter) + +from scatterdatamodifier import ScatterDataModifier + + +class ScatterGraph(QObject): + + def __init__(self, minimum_graph_size, maximum_graph_size): + super().__init__() + self._scatterGraph = Q3DScatter() + self._scatterWidget = QWidget() + hLayout = QHBoxLayout(self._scatterWidget) + self._scatterGraph.setMinimumSize(minimum_graph_size) + self._scatterGraph.setMaximumSize(maximum_graph_size) + self._scatterGraph.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + self._scatterGraph.setFocusPolicy(Qt.StrongFocus) + self._scatterGraph.setResizeMode(QQuickWidget.SizeRootObjectToView) + hLayout.addWidget(self._scatterGraph, 1) + + vLayout = QVBoxLayout() + hLayout.addLayout(vLayout) + + cameraButton = QCommandLinkButton(self._scatterWidget) + cameraButton.setText("Change camera preset") + cameraButton.setDescription("Switch between a number of preset camera positions") + cameraButton.setIconSize(QSize(0, 0)) + + itemCountButton = QCommandLinkButton(self._scatterWidget) + itemCountButton.setText("Toggle item count") + itemCountButton.setDescription("Switch between 900 and 10000 data points") + itemCountButton.setIconSize(QSize(0, 0)) + + rangeButton = QCommandLinkButton(self._scatterWidget) + rangeButton.setText("Toggle axis ranges") + rangeButton.setDescription("Switch between automatic axis ranges and preset ranges") + rangeButton.setIconSize(QSize(0, 0)) + + backgroundCheckBox = QCheckBox(self._scatterWidget) + backgroundCheckBox.setText("Show background") + backgroundCheckBox.setChecked(True) + + gridCheckBox = QCheckBox(self._scatterWidget) + gridCheckBox.setText("Show grid") + gridCheckBox.setChecked(True) + + smoothCheckBox = QCheckBox(self._scatterWidget) + smoothCheckBox.setText("Smooth dots") + smoothCheckBox.setChecked(True) + + itemStyleList = QComboBox(self._scatterWidget) + itemStyleList.addItem("Sphere", QAbstract3DSeries.Mesh.Sphere) + itemStyleList.addItem("Cube", QAbstract3DSeries.Mesh.Cube) + itemStyleList.addItem("Minimal", QAbstract3DSeries.Mesh.Minimal) + itemStyleList.addItem("Point", QAbstract3DSeries.Mesh.Point) + itemStyleList.setCurrentIndex(0) + + themeList = QComboBox(self._scatterWidget) + themeList.addItem("Qt") + themeList.addItem("Primary Colors") + themeList.addItem("Digia") + themeList.addItem("Stone Moss") + themeList.addItem("Army Blue") + themeList.addItem("Retro") + themeList.addItem("Ebony") + themeList.addItem("Isabelle") + themeList.setCurrentIndex(3) + + shadowQuality = QComboBox(self._scatterWidget) + shadowQuality.addItem("None") + shadowQuality.addItem("Low") + shadowQuality.addItem("Medium") + shadowQuality.addItem("High") + shadowQuality.addItem("Low Soft") + shadowQuality.addItem("Medium Soft") + shadowQuality.addItem("High Soft") + shadowQuality.setCurrentIndex(6) + + vLayout.addWidget(cameraButton) + vLayout.addWidget(itemCountButton) + vLayout.addWidget(rangeButton) + vLayout.addWidget(backgroundCheckBox) + vLayout.addWidget(gridCheckBox) + vLayout.addWidget(smoothCheckBox) + vLayout.addWidget(QLabel("Change dot style")) + vLayout.addWidget(itemStyleList) + vLayout.addWidget(QLabel("Change theme")) + vLayout.addWidget(themeList) + vLayout.addWidget(QLabel("Adjust shadow quality")) + vLayout.addWidget(shadowQuality, 1, Qt.AlignTop) + + self._modifier = ScatterDataModifier(self._scatterGraph, self) + + cameraButton.clicked.connect(self._modifier.changePresetCamera) + itemCountButton.clicked.connect(self._modifier.toggleItemCount) + rangeButton.clicked.connect(self._modifier.toggleRanges) + + backgroundCheckBox.stateChanged.connect(self._modifier.setBackgroundEnabled) + gridCheckBox.stateChanged.connect(self._modifier.setGridEnabled) + smoothCheckBox.stateChanged.connect(self._modifier.setSmoothDots) + + self._modifier.backgroundEnabledChanged.connect(backgroundCheckBox.setChecked) + self._modifier.gridEnabledChanged.connect(gridCheckBox.setChecked) + itemStyleList.currentIndexChanged.connect(self._modifier.changeStyle) + + themeList.currentIndexChanged.connect(self._modifier.changeTheme) + + shadowQuality.currentIndexChanged.connect(self._modifier.changeShadowQuality) + + self._modifier.shadowQualityChanged.connect(shadowQuality.setCurrentIndex) + self._scatterGraph.shadowQualityChanged.connect(self._modifier.shadowQualityUpdatedByVisual) + + def scatterWidget(self): + return self._scatterWidget diff --git a/examples/graphs/3d/widgetgallery/surfacegraph.py b/examples/graphs/3d/widgetgallery/surfacegraph.py new file mode 100644 index 00000000..4052da82 --- /dev/null +++ b/examples/graphs/3d/widgetgallery/surfacegraph.py @@ -0,0 +1,256 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from surfacegraphmodifier import SurfaceGraphModifier + +from PySide6.QtCore import QObject, Qt +from PySide6.QtGui import QBrush, QIcon, QLinearGradient, QPainter, QPixmap +from PySide6.QtWidgets import (QGroupBox, QCheckBox, QLabel, QHBoxLayout, + QPushButton, QRadioButton, QSizePolicy, QSlider, + QVBoxLayout, QWidget) +from PySide6.QtQuickWidgets import QQuickWidget +from PySide6.QtGraphs import Q3DSurface + + +def gradientBtoYPB_Pixmap(): + grBtoY = QLinearGradient(0, 0, 1, 100) + grBtoY.setColorAt(1.0, Qt.black) + grBtoY.setColorAt(0.67, Qt.blue) + grBtoY.setColorAt(0.33, Qt.red) + grBtoY.setColorAt(0.0, Qt.yellow) + pm = QPixmap(24, 100) + with QPainter(pm) as pmp: + pmp.setBrush(QBrush(grBtoY)) + pmp.setPen(Qt.NoPen) + pmp.drawRect(0, 0, 24, 100) + return pm + + +def gradientGtoRPB_Pixmap(): + grGtoR = QLinearGradient(0, 0, 1, 100) + grGtoR.setColorAt(1.0, Qt.darkGreen) + grGtoR.setColorAt(0.5, Qt.yellow) + grGtoR.setColorAt(0.2, Qt.red) + grGtoR.setColorAt(0.0, Qt.darkRed) + pm = QPixmap(24, 100) + with QPainter(pm) as pmp: + pmp.setBrush(QBrush(grGtoR)) + pmp.setPen(Qt.NoPen) + pmp.drawRect(0, 0, 24, 100) + return pm + + +def highlightPixmap(): + HEIGHT = 400 + WIDTH = 110 + BORDER = 10 + gr = QLinearGradient(0, 0, 1, HEIGHT - 2 * BORDER) + gr.setColorAt(1.0, Qt.black) + gr.setColorAt(0.8, Qt.darkGreen) + gr.setColorAt(0.6, Qt.green) + gr.setColorAt(0.4, Qt.yellow) + gr.setColorAt(0.2, Qt.red) + gr.setColorAt(0.0, Qt.darkRed) + pmHighlight = QPixmap(WIDTH, HEIGHT) + pmHighlight.fill(Qt.transparent) + with QPainter(pmHighlight) as pmpHighlight: + pmpHighlight.setBrush(QBrush(gr)) + pmpHighlight.setPen(Qt.NoPen) + pmpHighlight.drawRect(BORDER, BORDER, 35, HEIGHT - 2 * BORDER) + pmpHighlight.setPen(Qt.black) + step = (HEIGHT - 2 * BORDER) / 5 + for i in range(0, 6): + yPos = i * step + BORDER + pmpHighlight.drawLine(BORDER, yPos, 55, yPos) + HEIGHT = 550 - (i * 110) + pmpHighlight.drawText(60, yPos + 2, f"{HEIGHT} m") + return pmHighlight + + +class SurfaceGraph(QObject): + + def __init__(self, minimum_graph_size, maximum_graph_size): + super().__init__() + self._surfaceGraph = Q3DSurface() + self._surfaceWidget = QWidget() + hLayout = QHBoxLayout(self._surfaceWidget) + self._surfaceGraph.setMinimumSize(minimum_graph_size) + self._surfaceGraph.setMaximumSize(maximum_graph_size) + self._surfaceGraph.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + self._surfaceGraph.setFocusPolicy(Qt.StrongFocus) + self._surfaceGraph.setResizeMode(QQuickWidget.SizeRootObjectToView) + hLayout.addWidget(self._surfaceGraph, 1) + vLayout = QVBoxLayout() + hLayout.addLayout(vLayout) + vLayout.setAlignment(Qt.AlignTop) + # Create control widgets + modelGroupBox = QGroupBox("Model") + sqrtSinModelRB = QRadioButton(self._surfaceWidget) + sqrtSinModelRB.setText("Sqrt and Sin") + sqrtSinModelRB.setChecked(False) + heightMapModelRB = QRadioButton(self._surfaceWidget) + heightMapModelRB.setText("Multiseries\nHeight Map") + heightMapModelRB.setChecked(False) + texturedModelRB = QRadioButton(self._surfaceWidget) + texturedModelRB.setText("Textured\nTopography") + texturedModelRB.setChecked(False) + modelVBox = QVBoxLayout() + modelVBox.addWidget(sqrtSinModelRB) + modelVBox.addWidget(heightMapModelRB) + modelVBox.addWidget(texturedModelRB) + modelGroupBox.setLayout(modelVBox) + selectionGroupBox = QGroupBox("Graph Selection Mode") + modeNoneRB = QRadioButton(self._surfaceWidget) + modeNoneRB.setText("No selection") + modeNoneRB.setChecked(False) + modeItemRB = QRadioButton(self._surfaceWidget) + modeItemRB.setText("Item") + modeItemRB.setChecked(False) + modeSliceRowRB = QRadioButton(self._surfaceWidget) + modeSliceRowRB.setText("Row Slice") + modeSliceRowRB.setChecked(False) + modeSliceColumnRB = QRadioButton(self._surfaceWidget) + modeSliceColumnRB.setText("Column Slice") + modeSliceColumnRB.setChecked(False) + selectionVBox = QVBoxLayout() + selectionVBox.addWidget(modeNoneRB) + selectionVBox.addWidget(modeItemRB) + selectionVBox.addWidget(modeSliceRowRB) + selectionVBox.addWidget(modeSliceColumnRB) + selectionGroupBox.setLayout(selectionVBox) + axisGroupBox = QGroupBox("Axis ranges") + axisMinSliderX = QSlider(Qt.Horizontal) + axisMinSliderX.setMinimum(0) + axisMinSliderX.setTickInterval(1) + axisMinSliderX.setEnabled(True) + axisMaxSliderX = QSlider(Qt.Horizontal) + axisMaxSliderX.setMinimum(1) + axisMaxSliderX.setTickInterval(1) + axisMaxSliderX.setEnabled(True) + axisMinSliderZ = QSlider(Qt.Horizontal) + axisMinSliderZ.setMinimum(0) + axisMinSliderZ.setTickInterval(1) + axisMinSliderZ.setEnabled(True) + axisMaxSliderZ = QSlider(Qt.Horizontal) + axisMaxSliderZ.setMinimum(1) + axisMaxSliderZ.setTickInterval(1) + axisMaxSliderZ.setEnabled(True) + axisVBox = QVBoxLayout(axisGroupBox) + axisVBox.addWidget(QLabel("Column range")) + axisVBox.addWidget(axisMinSliderX) + axisVBox.addWidget(axisMaxSliderX) + axisVBox.addWidget(QLabel("Row range")) + axisVBox.addWidget(axisMinSliderZ) + axisVBox.addWidget(axisMaxSliderZ) + # Mode-dependent controls + # sqrt-sin + colorGroupBox = QGroupBox("Custom gradient") + + pixmap = gradientBtoYPB_Pixmap() + gradientBtoYPB = QPushButton(self._surfaceWidget) + gradientBtoYPB.setIcon(QIcon(pixmap)) + gradientBtoYPB.setIconSize(pixmap.size()) + + pixmap = gradientGtoRPB_Pixmap() + gradientGtoRPB = QPushButton(self._surfaceWidget) + gradientGtoRPB.setIcon(QIcon(pixmap)) + gradientGtoRPB.setIconSize(pixmap.size()) + + colorHBox = QHBoxLayout(colorGroupBox) + colorHBox.addWidget(gradientBtoYPB) + colorHBox.addWidget(gradientGtoRPB) + # Multiseries heightmap + showGroupBox = QGroupBox("Show Object") + showGroupBox.setVisible(False) + checkboxShowOilRigOne = QCheckBox("Oil Rig 1") + checkboxShowOilRigOne.setChecked(True) + checkboxShowOilRigTwo = QCheckBox("Oil Rig 2") + checkboxShowOilRigTwo.setChecked(True) + checkboxShowRefinery = QCheckBox("Refinery") + showVBox = QVBoxLayout() + showVBox.addWidget(checkboxShowOilRigOne) + showVBox.addWidget(checkboxShowOilRigTwo) + showVBox.addWidget(checkboxShowRefinery) + showGroupBox.setLayout(showVBox) + visualsGroupBox = QGroupBox("Visuals") + visualsGroupBox.setVisible(False) + checkboxVisualsSeeThrough = QCheckBox("See-Through") + checkboxHighlightOil = QCheckBox("Highlight Oil") + checkboxShowShadows = QCheckBox("Shadows") + checkboxShowShadows.setChecked(True) + visualVBox = QVBoxLayout(visualsGroupBox) + visualVBox.addWidget(checkboxVisualsSeeThrough) + visualVBox.addWidget(checkboxHighlightOil) + visualVBox.addWidget(checkboxShowShadows) + labelSelection = QLabel("Selection:") + labelSelection.setVisible(False) + labelSelectedItem = QLabel("Nothing") + labelSelectedItem.setVisible(False) + # Textured topography heightmap + enableTexture = QCheckBox("Surface texture") + enableTexture.setVisible(False) + + label = QLabel(self._surfaceWidget) + label.setPixmap(highlightPixmap()) + heightMapGroupBox = QGroupBox("Highlight color map") + colorMapVBox = QVBoxLayout() + colorMapVBox.addWidget(label) + heightMapGroupBox.setLayout(colorMapVBox) + heightMapGroupBox.setVisible(False) + # Populate vertical layout + # Common + vLayout.addWidget(modelGroupBox) + vLayout.addWidget(selectionGroupBox) + vLayout.addWidget(axisGroupBox) + # Sqrt Sin + vLayout.addWidget(colorGroupBox) + # Multiseries heightmap + vLayout.addWidget(showGroupBox) + vLayout.addWidget(visualsGroupBox) + vLayout.addWidget(labelSelection) + vLayout.addWidget(labelSelectedItem) + # Textured topography + vLayout.addWidget(heightMapGroupBox) + vLayout.addWidget(enableTexture) + # Create the controller + modifier = SurfaceGraphModifier(self._surfaceGraph, labelSelectedItem, self) + # Connect widget controls to controller + heightMapModelRB.toggled.connect(modifier.enableHeightMapModel) + sqrtSinModelRB.toggled.connect(modifier.enableSqrtSinModel) + texturedModelRB.toggled.connect(modifier.enableTopographyModel) + modeNoneRB.toggled.connect(modifier.toggleModeNone) + modeItemRB.toggled.connect(modifier.toggleModeItem) + modeSliceRowRB.toggled.connect(modifier.toggleModeSliceRow) + modeSliceColumnRB.toggled.connect(modifier.toggleModeSliceColumn) + axisMinSliderX.valueChanged.connect(modifier.adjustXMin) + axisMaxSliderX.valueChanged.connect(modifier.adjustXMax) + axisMinSliderZ.valueChanged.connect(modifier.adjustZMin) + axisMaxSliderZ.valueChanged.connect(modifier.adjustZMax) + # Mode dependent connections + gradientBtoYPB.pressed.connect(modifier.setBlackToYellowGradient) + gradientGtoRPB.pressed.connect(modifier.setGreenToRedGradient) + checkboxShowOilRigOne.stateChanged.connect(modifier.toggleItemOne) + checkboxShowOilRigTwo.stateChanged.connect(modifier.toggleItemTwo) + checkboxShowRefinery.stateChanged.connect(modifier.toggleItemThree) + checkboxVisualsSeeThrough.stateChanged.connect(modifier.toggleSeeThrough) + checkboxHighlightOil.stateChanged.connect(modifier.toggleOilHighlight) + checkboxShowShadows.stateChanged.connect(modifier.toggleShadows) + enableTexture.stateChanged.connect(modifier.toggleSurfaceTexture) + # Connections to disable features depending on mode + sqrtSinModelRB.toggled.connect(colorGroupBox.setVisible) + heightMapModelRB.toggled.connect(showGroupBox.setVisible) + heightMapModelRB.toggled.connect(visualsGroupBox.setVisible) + heightMapModelRB.toggled.connect(labelSelection.setVisible) + heightMapModelRB.toggled.connect(labelSelectedItem.setVisible) + texturedModelRB.toggled.connect(enableTexture.setVisible) + texturedModelRB.toggled.connect(heightMapGroupBox.setVisible) + modifier.setAxisMinSliderX(axisMinSliderX) + modifier.setAxisMaxSliderX(axisMaxSliderX) + modifier.setAxisMinSliderZ(axisMinSliderZ) + modifier.setAxisMaxSliderZ(axisMaxSliderZ) + sqrtSinModelRB.setChecked(True) + modeItemRB.setChecked(True) + enableTexture.setChecked(True) + + def surfaceWidget(self): + return self._surfaceWidget diff --git a/examples/graphs/3d/widgetgallery/surfacegraphmodifier.py b/examples/graphs/3d/widgetgallery/surfacegraphmodifier.py new file mode 100644 index 00000000..b2706c6f --- /dev/null +++ b/examples/graphs/3d/widgetgallery/surfacegraphmodifier.py @@ -0,0 +1,641 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import os +from math import sqrt, sin +from pathlib import Path + +from PySide6.QtCore import QObject, QPropertyAnimation, Qt, Slot +from PySide6.QtGui import (QColor, QFont, QImage, QLinearGradient, + QQuaternion, QVector3D) +from PySide6.QtGraphs import (QAbstract3DGraph, QCustom3DItem, + QCustom3DLabel, QHeightMapSurfaceDataProxy, + QValue3DAxis, QSurfaceDataItem, + QSurfaceDataProxy, QSurface3DSeries, + Q3DInputHandler, Q3DTheme) + + +from highlightseries import HighlightSeries +from topographicseries import TopographicSeries +from custominputhandler import CustomInputHandler + + +SAMPLE_COUNT_X = 150 +SAMPLE_COUNT_Z = 150 +HEIGHTMAP_GRID_STEP_X = 6 +HEIGHTMAP_GRID_STEP_Z = 6 +SAMPLE_MIN = -8.0 +SAMPLE_MAX = 8.0 + +AREA_WIDTH = 8000.0 +AREA_HEIGHT = 8000.0 +ASPECT_RATIO = 0.1389 +MIN_RANGE = AREA_WIDTH * 0.49 + + +class SurfaceGraphModifier(QObject): + + def __init__(self, surface, label, parent): + super().__init__(parent) + self._data_path = Path(__file__).resolve().parent / "data" + self._graph = surface + self._textField = label + self._sqrtSinProxy = None + self._sqrtSinSeries = None + self._heightMapProxyOne = None + self._heightMapProxyTwo = None + self._heightMapProxyThree = None + self._heightMapSeriesOne = None + self._heightMapSeriesTwo = None + self._heightMapSeriesThree = None + + self._axisMinSliderX = None + self._axisMaxSliderX = None + self._axisMinSliderZ = None + self._axisMaxSliderZ = None + self._rangeMinX = 0.0 + self._rangeMinZ = 0.0 + self._stepX = 0.0 + self._stepZ = 0.0 + self._heightMapWidth = 0 + self._heightMapHeight = 0 + + self._selectionAnimation = None + self._titleLabel = None + self._previouslyAnimatedItem = None + self._previousScaling = {} + + self._topography = None + self._highlight = None + self._highlightWidth = 0 + self._highlightHeight = 0 + + self._customInputHandler = None + self._defaultInputHandler = Q3DInputHandler() + + self._graph.setCameraZoomLevel(85.0) + self._graph.setCameraPreset(QAbstract3DGraph.CameraPreset.IsometricRight) + self._graph.activeTheme().setType(Q3DTheme.Theme.Retro) + + self._x_axis = QValue3DAxis() + self._y_axis = QValue3DAxis() + self._z_axis = QValue3DAxis() + self._graph.setAxisX(self._x_axis) + self._graph.setAxisY(self._y_axis) + self._graph.setAxisZ(self._z_axis) + + # + # Sqrt Sin + # + self._sqrtSinProxy = QSurfaceDataProxy() + self._sqrtSinSeries = QSurface3DSeries(self._sqrtSinProxy) + self.fillSqrtSinProxy() + + # + # Multisurface heightmap + # + # Create the first surface layer + heightMapImageOne = QImage(self._data_path / "layer_1.png") + self._heightMapProxyOne = QHeightMapSurfaceDataProxy(heightMapImageOne) + self._heightMapSeriesOne = QSurface3DSeries(self._heightMapProxyOne) + self._heightMapSeriesOne.setItemLabelFormat("(@xLabel, @zLabel): @yLabel") + self._heightMapProxyOne.setValueRanges(34.0, 40.0, 18.0, 24.0) + + # Create the other 2 surface layers + heightMapImageTwo = QImage(self._data_path / "layer_2.png") + self._heightMapProxyTwo = QHeightMapSurfaceDataProxy(heightMapImageTwo) + self._heightMapSeriesTwo = QSurface3DSeries(self._heightMapProxyTwo) + self._heightMapSeriesTwo.setItemLabelFormat("(@xLabel, @zLabel): @yLabel") + self._heightMapProxyTwo.setValueRanges(34.0, 40.0, 18.0, 24.0) + + heightMapImageThree = QImage(self._data_path / "layer_3.png") + self._heightMapProxyThree = QHeightMapSurfaceDataProxy(heightMapImageThree) + self._heightMapSeriesThree = QSurface3DSeries(self._heightMapProxyThree) + self._heightMapSeriesThree.setItemLabelFormat("(@xLabel, @zLabel): @yLabel") + self._heightMapProxyThree.setValueRanges(34.0, 40.0, 18.0, 24.0) + + # The images are the same size, so it's enough to get the dimensions + # from one + self._heightMapWidth = heightMapImageOne.width() + self._heightMapHeight = heightMapImageOne.height() + + # Set the gradients for multi-surface layers + grOne = QLinearGradient() + grOne.setColorAt(0.0, Qt.black) + grOne.setColorAt(0.38, Qt.darkYellow) + grOne.setColorAt(0.39, Qt.darkGreen) + grOne.setColorAt(0.5, Qt.darkGray) + grOne.setColorAt(1.0, Qt.gray) + self._heightMapSeriesOne.setBaseGradient(grOne) + self._heightMapSeriesOne.setColorStyle(Q3DTheme.ColorStyle.RangeGradient) + + grTwo = QLinearGradient() + grTwo.setColorAt(0.39, Qt.blue) + grTwo.setColorAt(0.4, Qt.white) + self._heightMapSeriesTwo.setBaseGradient(grTwo) + self._heightMapSeriesTwo.setColorStyle(Q3DTheme.ColorStyle.RangeGradient) + + grThree = QLinearGradient() + grThree.setColorAt(0.0, Qt.white) + grThree.setColorAt(0.05, Qt.black) + self._heightMapSeriesThree.setBaseGradient(grThree) + self._heightMapSeriesThree.setColorStyle(Q3DTheme.ColorStyle.RangeGradient) + + # Custom items and label + self._graph.selectedElementChanged.connect(self.handleElementSelected) + + self._selectionAnimation = QPropertyAnimation(self) + self._selectionAnimation.setPropertyName(b"scaling") + self._selectionAnimation.setDuration(500) + self._selectionAnimation.setLoopCount(-1) + + titleFont = QFont("Century Gothic", 30) + titleFont.setBold(True) + self._titleLabel = QCustom3DLabel("Oil Rigs on Imaginary Sea", titleFont, + QVector3D(0.0, 1.2, 0.0), + QVector3D(1.0, 1.0, 0.0), + QQuaternion()) + self._titleLabel.setPositionAbsolute(True) + self._titleLabel.setFacingCamera(True) + self._titleLabel.setBackgroundColor(QColor(0x66cdaa)) + self._graph.addCustomItem(self._titleLabel) + self._titleLabel.setVisible(False) + + # Make two of the custom object visible + self.toggleItemOne(True) + self.toggleItemTwo(True) + + # + # Topographic map + # + self._topography = TopographicSeries() + file_name = os.fspath(self._data_path / "topography.png") + self._topography.setTopographyFile(file_name, AREA_WIDTH, AREA_HEIGHT) + self._topography.setItemLabelFormat("@yLabel m") + + self._highlight = HighlightSeries() + self._highlight.setTopographicSeries(self._topography) + self._highlight.setMinHeight(MIN_RANGE * ASPECT_RATIO) + self._highlight.handleGradientChange(AREA_WIDTH * ASPECT_RATIO) + self._graph.axisY().maxChanged.connect(self._highlight.handleGradientChange) + + self._customInputHandler = CustomInputHandler(self._graph) + self._customInputHandler.setHighlightSeries(self._highlight) + self._customInputHandler.setAxes(self._x_axis, self._y_axis, self._z_axis) + self._customInputHandler.setLimits(0.0, AREA_WIDTH, MIN_RANGE) + self._customInputHandler.setAspectRatio(ASPECT_RATIO) + + def fillSqrtSinProxy(self): + stepX = (SAMPLE_MAX - SAMPLE_MIN) / float(SAMPLE_COUNT_X - 1) + stepZ = (SAMPLE_MAX - SAMPLE_MIN) / float(SAMPLE_COUNT_Z - 1) + + dataArray = [] + for i in range(0, SAMPLE_COUNT_Z): + newRow = [] + # Keep values within range bounds, since just adding step can + # cause minor drift due to the rounding errors. + z = min(SAMPLE_MAX, (i * stepZ + SAMPLE_MIN)) + for j in range(0, SAMPLE_COUNT_X): + x = min(SAMPLE_MAX, (j * stepX + SAMPLE_MIN)) + R = sqrt(z * z + x * x) + 0.01 + y = (sin(R) / R + 0.24) * 1.61 + item = QSurfaceDataItem(QVector3D(x, y, z)) + newRow.append(item) + dataArray.append(newRow) + self._sqrtSinProxy.resetArray(dataArray) + + @Slot(bool) + def enableSqrtSinModel(self, enable): + if enable: + self._sqrtSinSeries.setDrawMode(QSurface3DSeries.DrawSurfaceAndWireframe) + self._sqrtSinSeries.setFlatShadingEnabled(True) + + self._graph.axisX().setLabelFormat("%.2f") + self._graph.axisZ().setLabelFormat("%.2f") + self._graph.axisX().setRange(SAMPLE_MIN, SAMPLE_MAX) + self._graph.axisY().setRange(0.0, 2.0) + self._graph.axisZ().setRange(SAMPLE_MIN, SAMPLE_MAX) + self._graph.axisX().setLabelAutoRotation(30.0) + self._graph.axisY().setLabelAutoRotation(90.0) + self._graph.axisZ().setLabelAutoRotation(30.0) + + self._graph.removeSeries(self._heightMapSeriesOne) + self._graph.removeSeries(self._heightMapSeriesTwo) + self._graph.removeSeries(self._heightMapSeriesThree) + self._graph.removeSeries(self._topography) + self._graph.removeSeries(self._highlight) + + self._graph.addSeries(self._sqrtSinSeries) + + self._titleLabel.setVisible(False) + self._graph.axisX().setTitleVisible(False) + self._graph.axisY().setTitleVisible(False) + self._graph.axisZ().setTitleVisible(False) + + self._graph.axisX().setTitle("") + self._graph.axisY().setTitle("") + self._graph.axisZ().setTitle("") + + self._graph.setActiveInputHandler(self._defaultInputHandler) + + # Reset range sliders for Sqrt & Sin + self._rangeMinX = SAMPLE_MIN + self._rangeMinZ = SAMPLE_MIN + self._stepX = (SAMPLE_MAX - SAMPLE_MIN) / float(SAMPLE_COUNT_X - 1) + self._stepZ = (SAMPLE_MAX - SAMPLE_MIN) / float(SAMPLE_COUNT_Z - 1) + self._axisMinSliderX.setMinimum(0) + self._axisMinSliderX.setMaximum(SAMPLE_COUNT_X - 2) + self._axisMinSliderX.setValue(0) + self._axisMaxSliderX.setMinimum(1) + self._axisMaxSliderX.setMaximum(SAMPLE_COUNT_X - 1) + self._axisMaxSliderX.setValue(SAMPLE_COUNT_X - 1) + self._axisMinSliderZ.setMinimum(0) + self._axisMinSliderZ.setMaximum(SAMPLE_COUNT_Z - 2) + self._axisMinSliderZ.setValue(0) + self._axisMaxSliderZ.setMinimum(1) + self._axisMaxSliderZ.setMaximum(SAMPLE_COUNT_Z - 1) + self._axisMaxSliderZ.setValue(SAMPLE_COUNT_Z - 1) + + @Slot(bool) + def enableHeightMapModel(self, enable): + if enable: + self._heightMapSeriesOne.setDrawMode(QSurface3DSeries.DrawSurface) + self._heightMapSeriesOne.setFlatShadingEnabled(False) + self._heightMapSeriesTwo.setDrawMode(QSurface3DSeries.DrawSurface) + self._heightMapSeriesTwo.setFlatShadingEnabled(False) + self._heightMapSeriesThree.setDrawMode(QSurface3DSeries.DrawSurface) + self._heightMapSeriesThree.setFlatShadingEnabled(False) + + self._graph.axisX().setLabelFormat("%.1f N") + self._graph.axisZ().setLabelFormat("%.1f E") + self._graph.axisX().setRange(34.0, 40.0) + self._graph.axisY().setAutoAdjustRange(True) + self._graph.axisZ().setRange(18.0, 24.0) + + self._graph.axisX().setTitle("Latitude") + self._graph.axisY().setTitle("Height") + self._graph.axisZ().setTitle("Longitude") + + self._graph.removeSeries(self._sqrtSinSeries) + self._graph.removeSeries(self._topography) + self._graph.removeSeries(self._highlight) + self._graph.addSeries(self._heightMapSeriesOne) + self._graph.addSeries(self._heightMapSeriesTwo) + self._graph.addSeries(self._heightMapSeriesThree) + + self._graph.setActiveInputHandler(self._defaultInputHandler) + + self._titleLabel.setVisible(True) + self._graph.axisX().setTitleVisible(True) + self._graph.axisY().setTitleVisible(True) + self._graph.axisZ().setTitleVisible(True) + + # Reset range sliders for height map + mapGridCountX = self._heightMapWidth / HEIGHTMAP_GRID_STEP_X + mapGridCountZ = self._heightMapHeight / HEIGHTMAP_GRID_STEP_Z + self._rangeMinX = 34.0 + self._rangeMinZ = 18.0 + self._stepX = 6.0 / float(mapGridCountX - 1) + self._stepZ = 6.0 / float(mapGridCountZ - 1) + self._axisMinSliderX.setMinimum(0) + self._axisMinSliderX.setMaximum(mapGridCountX - 2) + self._axisMinSliderX.setValue(0) + self._axisMaxSliderX.setMinimum(1) + self._axisMaxSliderX.setMaximum(mapGridCountX - 1) + self._axisMaxSliderX.setValue(mapGridCountX - 1) + self._axisMinSliderZ.setMinimum(0) + self._axisMinSliderZ.setMaximum(mapGridCountZ - 2) + self._axisMinSliderZ.setValue(0) + self._axisMaxSliderZ.setMinimum(1) + self._axisMaxSliderZ.setMaximum(mapGridCountZ - 1) + self._axisMaxSliderZ.setValue(mapGridCountZ - 1) + + @Slot(bool) + def enableTopographyModel(self, enable): + if enable: + self._graph.axisX().setLabelFormat("%i") + self._graph.axisZ().setLabelFormat("%i") + self._graph.axisX().setRange(0.0, AREA_WIDTH) + self._graph.axisY().setRange(100.0, AREA_WIDTH * ASPECT_RATIO) + self._graph.axisZ().setRange(0.0, AREA_HEIGHT) + self._graph.axisX().setLabelAutoRotation(30.0) + self._graph.axisY().setLabelAutoRotation(90.0) + self._graph.axisZ().setLabelAutoRotation(30.0) + + self._graph.removeSeries(self._heightMapSeriesOne) + self._graph.removeSeries(self._heightMapSeriesTwo) + self._graph.removeSeries(self._heightMapSeriesThree) + self._graph.addSeries(self._topography) + self._graph.addSeries(self._highlight) + + self._titleLabel.setVisible(False) + self._graph.axisX().setTitleVisible(False) + self._graph.axisY().setTitleVisible(False) + self._graph.axisZ().setTitleVisible(False) + + self._graph.axisX().setTitle("") + self._graph.axisY().setTitle("") + self._graph.axisZ().setTitle("") + + self._graph.setActiveInputHandler(self._customInputHandler) + + # Reset range sliders for topography map + self._rangeMinX = 0.0 + self._rangeMinZ = 0.0 + self._stepX = 1.0 + self._stepZ = 1.0 + self._axisMinSliderX.setMinimum(0) + self._axisMinSliderX.setMaximum(AREA_WIDTH - 200) + self._axisMinSliderX.setValue(0) + self._axisMaxSliderX.setMinimum(200) + self._axisMaxSliderX.setMaximum(AREA_WIDTH) + self._axisMaxSliderX.setValue(AREA_WIDTH) + self._axisMinSliderZ.setMinimum(0) + self._axisMinSliderZ.setMaximum(AREA_HEIGHT - 200) + self._axisMinSliderZ.setValue(0) + self._axisMaxSliderZ.setMinimum(200) + self._axisMaxSliderZ.setMaximum(AREA_HEIGHT) + self._axisMaxSliderZ.setValue(AREA_HEIGHT) + + def adjustXMin(self, min): + minX = self._stepX * float(min) + self._rangeMinX + + max = self._axisMaxSliderX.value() + if min >= max: + max = min + 1 + self._axisMaxSliderX.setValue(max) + + maxX = self._stepX * max + self._rangeMinX + + self.setAxisXRange(minX, maxX) + + def adjustXMax(self, max): + maxX = self._stepX * float(max) + self._rangeMinX + + min = self._axisMinSliderX.value() + if max <= min: + min = max - 1 + self._axisMinSliderX.setValue(min) + + minX = self._stepX * min + self._rangeMinX + + self.setAxisXRange(minX, maxX) + + def adjustZMin(self, min): + minZ = self._stepZ * float(min) + self._rangeMinZ + + max = self._axisMaxSliderZ.value() + if min >= max: + max = min + 1 + self._axisMaxSliderZ.setValue(max) + + maxZ = self._stepZ * max + self._rangeMinZ + + self.setAxisZRange(minZ, maxZ) + + def adjustZMax(self, max): + maxX = self._stepZ * float(max) + self._rangeMinZ + + min = self._axisMinSliderZ.value() + if max <= min: + min = max - 1 + self._axisMinSliderZ.setValue(min) + + minX = self._stepZ * min + self._rangeMinZ + + self.setAxisZRange(minX, maxX) + + def setAxisXRange(self, min, max): + self._graph.axisX().setRange(min, max) + + def setAxisZRange(self, min, max): + self._graph.axisZ().setRange(min, max) + + def setBlackToYellowGradient(self): + gr = QLinearGradient() + gr.setColorAt(0.0, Qt.black) + gr.setColorAt(0.33, Qt.blue) + gr.setColorAt(0.67, Qt.red) + gr.setColorAt(1.0, Qt.yellow) + + self._sqrtSinSeries.setBaseGradient(gr) + self._sqrtSinSeries.setColorStyle(Q3DTheme.ColorStyle.RangeGradient) + + def setGreenToRedGradient(self): + gr = QLinearGradient() + gr.setColorAt(0.0, Qt.darkGreen) + gr.setColorAt(0.5, Qt.yellow) + gr.setColorAt(0.8, Qt.red) + gr.setColorAt(1.0, Qt.darkRed) + + self._sqrtSinSeries.setBaseGradient(gr) + self._sqrtSinSeries.setColorStyle(Q3DTheme.ColorStyle.RangeGradient) + + @Slot(bool) + def toggleItemOne(self, show): + positionOne = QVector3D(39.0, 77.0, 19.2) + positionOnePipe = QVector3D(39.0, 45.0, 19.2) + positionOneLabel = QVector3D(39.0, 107.0, 19.2) + if show: + color = QImage(2, 2, QImage.Format_RGB32) + color.fill(Qt.red) + file_name = os.fspath(self._data_path / "oilrig.mesh") + item = QCustom3DItem(file_name, positionOne, + QVector3D(0.025, 0.025, 0.025), + QQuaternion.fromAxisAndAngle(0.0, 1.0, 0.0, 45.0), + color) + self._graph.addCustomItem(item) + file_name = os.fspath(self._data_path / "pipe.mesh") + item = QCustom3DItem(file_name, positionOnePipe, + QVector3D(0.005, 0.5, 0.005), QQuaternion(), + color) + item.setShadowCasting(False) + self._graph.addCustomItem(item) + + label = QCustom3DLabel() + label.setText("Oil Rig One") + label.setPosition(positionOneLabel) + label.setScaling(QVector3D(1.0, 1.0, 1.0)) + self._graph.addCustomItem(label) + else: + self.resetSelection() + self._graph.removeCustomItemAt(positionOne) + self._graph.removeCustomItemAt(positionOnePipe) + self._graph.removeCustomItemAt(positionOneLabel) + + @Slot(bool) + def toggleItemTwo(self, show): + positionTwo = QVector3D(34.5, 77.0, 23.4) + positionTwoPipe = QVector3D(34.5, 45.0, 23.4) + positionTwoLabel = QVector3D(34.5, 107.0, 23.4) + if show: + color = QImage(2, 2, QImage.Format_RGB32) + color.fill(Qt.red) + item = QCustom3DItem() + file_name = os.fspath(self._data_path / "oilrig.mesh") + item.setMeshFile(file_name) + item.setPosition(positionTwo) + item.setScaling(QVector3D(0.025, 0.025, 0.025)) + item.setRotation(QQuaternion.fromAxisAndAngle(0.0, 1.0, 0.0, 25.0)) + item.setTextureImage(color) + self._graph.addCustomItem(item) + file_name = os.fspath(self._data_path / "pipe.mesh") + item = QCustom3DItem(file_name, positionTwoPipe, + QVector3D(0.005, 0.5, 0.005), QQuaternion(), + color) + item.setShadowCasting(False) + self._graph.addCustomItem(item) + + label = QCustom3DLabel() + label.setText("Oil Rig Two") + label.setPosition(positionTwoLabel) + label.setScaling(QVector3D(1.0, 1.0, 1.0)) + self._graph.addCustomItem(label) + else: + self.resetSelection() + self._graph.removeCustomItemAt(positionTwo) + self._graph.removeCustomItemAt(positionTwoPipe) + self._graph.removeCustomItemAt(positionTwoLabel) + + @Slot(bool) + def toggleItemThree(self, show): + positionThree = QVector3D(34.5, 86.0, 19.1) + positionThreeLabel = QVector3D(34.5, 116.0, 19.1) + if show: + color = QImage(2, 2, QImage.Format_RGB32) + color.fill(Qt.darkMagenta) + item = QCustom3DItem() + file_name = os.fspath(self._data_path / "refinery.mesh") + item.setMeshFile(file_name) + item.setPosition(positionThree) + item.setScaling(QVector3D(0.04, 0.04, 0.04)) + item.setRotation(QQuaternion.fromAxisAndAngle(0.0, 1.0, 0.0, 75.0)) + item.setTextureImage(color) + self._graph.addCustomItem(item) + + label = QCustom3DLabel() + label.setText("Refinery") + label.setPosition(positionThreeLabel) + label.setScaling(QVector3D(1.0, 1.0, 1.0)) + self._graph.addCustomItem(label) + else: + self.resetSelection() + self._graph.removeCustomItemAt(positionThree) + self._graph.removeCustomItemAt(positionThreeLabel) + + @Slot(bool) + def toggleSeeThrough(self, seethrough): + s0 = self._graph.seriesList()[0] + s1 = self._graph.seriesList()[1] + if seethrough: + s0.setDrawMode(QSurface3DSeries.DrawWireframe) + s1.setDrawMode(QSurface3DSeries.DrawWireframe) + else: + s0.setDrawMode(QSurface3DSeries.DrawSurface) + s1.setDrawMode(QSurface3DSeries.DrawSurface) + + @Slot(bool) + def toggleOilHighlight(self, highlight): + s2 = self._graph.seriesList()[2] + if highlight: + grThree = QLinearGradient() + grThree.setColorAt(0.0, Qt.black) + grThree.setColorAt(0.05, Qt.red) + s2.setBaseGradient(grThree) + else: + grThree = QLinearGradient() + grThree.setColorAt(0.0, Qt.white) + grThree.setColorAt(0.05, Qt.black) + s2.setBaseGradient(grThree) + + @Slot(bool) + def toggleShadows(self, shadows): + sq = (QAbstract3DGraph.ShadowQualityMedium + if shadows else QAbstract3DGraph.ShadowQualityNone) + self._graph.setShadowQuality(sq) + + @Slot(bool) + def toggleSurfaceTexture(self, enable): + if enable: + file_name = os.fspath(self._data_path / "maptexture.jpg") + self._topography.setTextureFile(file_name) + else: + self._topography.setTextureFile("") + + def handleElementSelected(self, type): + self.resetSelection() + if type == QAbstract3DGraph.ElementCustomItem: + item = self._graph.selectedCustomItem() + text = "" + if isinstance(item, QCustom3DItem): + text += "Custom label: " + else: + file = item.meshFile().split("/")[-1] + text += f"{file}: " + + text += str(self._graph.selectedCustomItemIndex()) + self._textField.setText(text) + self._previouslyAnimatedItem = item + self._previousScaling = item.scaling() + self._selectionAnimation.setTargetObject(item) + self._selectionAnimation.setStartValue(item.scaling()) + self._selectionAnimation.setEndValue(item.scaling() * 1.5) + self._selectionAnimation.start() + elif type == QAbstract3DGraph.ElementSeries: + text = "Surface (" + series = self._graph.selectedSeries() + if series: + point = series.selectedPoint() + text += f"{point.x()}, {point.y()}" + text += ")" + self._textField.setText(text) + elif (type.value > QAbstract3DGraph.ElementSeries.value + and type < QAbstract3DGraph.ElementCustomItem.value): + index = self._graph.selectedLabelIndex() + text = "" + if type == QAbstract3DGraph.ElementAxisXLabel: + text += "Axis X label: " + elif type == QAbstract3DGraph.ElementAxisYLabel: + text += "Axis Y label: " + else: + text += "Axis Z label: " + text += str(index) + self._textField.setText(text) + else: + self._textField.setText("Nothing") + + def resetSelection(self): + self._selectionAnimation.stop() + if self._previouslyAnimatedItem: + self._previouslyAnimatedItem.setScaling(self._previousScaling) + self._previouslyAnimatedItem = None + + def toggleModeNone(self): + self._graph.setSelectionMode(QAbstract3DGraph.SelectionNone) + + def toggleModeItem(self): + self._graph.setSelectionMode(QAbstract3DGraph.SelectionItem) + + def toggleModeSliceRow(self): + sm = (QAbstract3DGraph.SelectionItemAndRow + | QAbstract3DGraph.SelectionSlice + | QAbstract3DGraph.SelectionMultiSeries) + self._graph.setSelectionMode(sm) + + def toggleModeSliceColumn(self): + sm = (QAbstract3DGraph.SelectionItemAndColumn + | QAbstract3DGraph.SelectionSlice + | QAbstract3DGraph.SelectionMultiSeries) + self._graph.setSelectionMode(sm) + + def setAxisMinSliderX(self, slider): + self._axisMinSliderX = slider + + def setAxisMaxSliderX(self, slider): + self._axisMaxSliderX = slider + + def setAxisMinSliderZ(self, slider): + self._axisMinSliderZ = slider + + def setAxisMaxSliderZ(self, slider): + self._axisMaxSliderZ = slider diff --git a/examples/graphs/3d/widgetgallery/topographicseries.py b/examples/graphs/3d/widgetgallery/topographicseries.py new file mode 100644 index 00000000..4f286a22 --- /dev/null +++ b/examples/graphs/3d/widgetgallery/topographicseries.py @@ -0,0 +1,57 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from PySide6.QtCore import Qt +from PySide6.QtGui import QImage, QVector3D +from PySide6.QtGraphs import (QSurface3DSeries, QSurfaceDataItem) + + +# Value used to encode height data as RGB value on PNG file +PACKING_FACTOR = 11983.0 + + +class TopographicSeries(QSurface3DSeries): + + def __init__(self): + super().__init__() + self._sampleCountX = 0.0 + self._sampleCountZ = 0.0 + self.setDrawMode(QSurface3DSeries.DrawSurface) + self.setFlatShadingEnabled(True) + self.setBaseColor(Qt.white) + + def sampleCountX(self): + return self._sampleCountX + + def sampleCountZ(self): + return self._sampleCountZ + + def setTopographyFile(self, file, width, height): + heightMapImage = QImage(file) + bits = heightMapImage.bits() + imageHeight = heightMapImage.height() + imageWidth = heightMapImage.width() + widthBits = imageWidth * 4 + stepX = width / float(imageWidth) + stepZ = height / float(imageHeight) + + dataArray = [] + for i in range(0, imageHeight): + p = i * widthBits + z = height - float(i) * stepZ + newRow = [] + for j in range(0, imageWidth): + aa = bits[p + 0] + rr = bits[p + 1] + gg = bits[p + 2] + color = (gg << 16) + (rr << 8) + aa + y = float(color) / PACKING_FACTOR + item = QSurfaceDataItem(QVector3D(float(j) * stepX, y, z)) + newRow.append(item) + p += 4 + dataArray.append(newRow) + + self.dataProxy().resetArray(dataArray) + + self._sampleCountX = float(imageWidth) + self._sampleCountZ = float(imageHeight) diff --git a/examples/graphs/3d/widgetgallery/variantbardatamapping.py b/examples/graphs/3d/widgetgallery/variantbardatamapping.py new file mode 100644 index 00000000..50bdefa6 --- /dev/null +++ b/examples/graphs/3d/widgetgallery/variantbardatamapping.py @@ -0,0 +1,67 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from PySide6.QtCore import QObject, Signal + + +class VariantBarDataMapping(QObject): + + rowIndexChanged = Signal() + columnIndexChanged = Signal() + valueIndexChanged = Signal() + rowCategoriesChanged = Signal() + columnCategoriesChanged = Signal() + mappingChanged = Signal() + + def __init__(self, rowIndex, columnIndex, valueIndex, + rowCategories=[], columnCategories=[]): + super().__init__(None) + self._rowIndex = rowIndex + self._columnIndex = columnIndex + self._valueIndex = valueIndex + self._rowCategories = rowCategories + self._columnCategories = columnCategories + + def setRowIndex(self, index): + self._rowIndex = index + self.mappingChanged.emit() + + def rowIndex(self): + return self._rowIndex + + def setColumnIndex(self, index): + self._columnIndex = index + self.mappingChanged.emit() + + def columnIndex(self): + return self._columnIndex + + def setValueIndex(self, index): + self._valueIndex = index + self.mappingChanged.emit() + + def valueIndex(self): + return self._valueIndex + + def setRowCategories(self, categories): + self._rowCategories = categories + self.mappingChanged.emit() + + def rowCategories(self): + return self._rowCategories + + def setColumnCategories(self, categories): + self._columnCategories = categories + self.mappingChanged.emit() + + def columnCategories(self): + return self._columnCategories + + def remap(self, rowIndex, columnIndex, valueIndex, + rowCategories=[], columnCategories=[]): + self._rowIndex = rowIndex + self._columnIndex = columnIndex + self._valueIndex = valueIndex + self._rowCategories = rowCategories + self._columnCategories = columnCategories + self.mappingChanged.emit() diff --git a/examples/graphs/3d/widgetgallery/variantbardataproxy.py b/examples/graphs/3d/widgetgallery/variantbardataproxy.py new file mode 100644 index 00000000..5ab2a2cd --- /dev/null +++ b/examples/graphs/3d/widgetgallery/variantbardataproxy.py @@ -0,0 +1,100 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from PySide6.QtCore import Slot +from PySide6.QtGraphs import QBarDataProxy, QBarDataItem + + +class VariantBarDataProxy(QBarDataProxy): + + def __init__(self): + super().__init__() + self._dataSet = None + self._mapping = None + + def setDataSet(self, newSet): + if self._dataSet: + self._dataSet.itemsAdded.disconnect(self.handleItemsAdded) + self._dataSet.dataCleared.disconnect(self.handleDataCleared) + + self._dataSet = newSet + + if self._dataSet: + self._dataSet.itemsAdded.connect(self.handleItemsAdded) + self._dataSet.dataCleared.connect(self.handleDataCleared) + self.resolveDataSet() + + def dataSet(self): + return self._dataSet.data() + + # Map key (row, column, value) to value index in data item (VariantItem). + # Doesn't gain ownership of mapping, but does connect to it to listen for + # mapping changes. Modifying mapping that is set to proxy will trigger + # dataset re-resolving. + def setMapping(self, mapping): + if self._mapping: + self._mapping.mappingChanged.disconnect(self.handleMappingChanged) + + self._mapping = mapping + + if self._mapping: + self._mapping.mappingChanged.connect(self.handleMappingChanged) + + self.resolveDataSet() + + def mapping(self): + return self._mapping.data() + + @Slot(int, int) + def handleItemsAdded(self, index, count): + # Resolve new items + self.resolveDataSet() + + @Slot() + def handleDataCleared(self): + # Data cleared, reset array + self.resetArray(None) + + @Slot() + def handleMappingChanged(self): + self.resolveDataSet() + + # Resolve entire dataset into QBarDataArray. + def resolveDataSet(self): + # If we have no data or mapping, or the categories are not defined, + # simply clear the array + if (not self._dataSet or not self._mapping + or not self._mapping.rowCategories() + or not self._mapping.columnCategories()): + self.resetArray() + return + + itemList = self._dataSet.itemList() + + rowIndex = self._mapping.rowIndex() + columnIndex = self._mapping.columnIndex() + valueIndex = self._mapping.valueIndex() + rowList = self._mapping.rowCategories() + columnList = self._mapping.columnCategories() + + # Sort values into rows and columns + itemValueMap = {} + for item in itemList: + key = str(item[rowIndex]) + v = itemValueMap.get(key) + if not v: + v = {} + itemValueMap[key] = v + v[str(item[columnIndex])] = float(item[valueIndex]) + + # Create a new data array in format the parent class understands + newProxyArray = [] + for rowKey in rowList: + newProxyRow = [] + for i in range(0, len(columnList)): + item = QBarDataItem(itemValueMap[rowKey][columnList[i]]) + newProxyRow.append(item) + newProxyArray.append(newProxyRow) + + # Finally, reset the data array in the parent class + self.resetArray(newProxyArray) diff --git a/examples/graphs/3d/widgetgallery/variantdataset.py b/examples/graphs/3d/widgetgallery/variantdataset.py new file mode 100644 index 00000000..752bc388 --- /dev/null +++ b/examples/graphs/3d/widgetgallery/variantdataset.py @@ -0,0 +1,39 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from PySide6.QtCore import QObject, Signal + + +class VariantDataSet(QObject): + + itemsAdded = Signal(int, int) + dataCleared = Signal() + + def __init__(self): + super().__init__() + self._variantData = [] + + def clear(self): + for item in self._variantData: + item.clear() + del item + + self._variantData.clear() + self.dataCleared.emit() + + def addItem(self, item): + self._variantData.append(item) + addIndex = len(self._variantData) + + self.itemsAdded.emit(addIndex, 1) + return addIndex + + def addItems(self, itemList): + newCount = len(itemList) + addIndex = len(self._variantData) + self._variantData.extend(itemList) + self.itemsAdded.emit(addIndex, newCount) + return addIndex + + def itemList(self): + return self._variantData diff --git a/examples/graphs/3d/widgetgallery/widgetgallery.pyproject b/examples/graphs/3d/widgetgallery/widgetgallery.pyproject new file mode 100644 index 00000000..581b2148 --- /dev/null +++ b/examples/graphs/3d/widgetgallery/widgetgallery.pyproject @@ -0,0 +1,29 @@ +{ + "files": ["main.py", + "axesinputhandler.py", + "bargraph.py", + "custominputhandler.py", + "graphmodifier.py", + "highlightseries.py", + "rainfalldata.py", + "scatterdatamodifier.py", + "scattergraph.py", + "surfacegraph.py", + "surfacegraphmodifier.py", + "topographicseries.py", + "variantbardatamapping.py", + "variantbardataproxy.py", + "variantdataset.py", + "data/layer_1.png", + "data/layer_2.png", + "data/layer_3.png", + "data/license.txt", + "data/maptexture.jpg", + "data/narrowarrow.mesh", + "data/oilrig.mesh", + "data/pipe.mesh", + "data/raindata.txt", + "data/refinery.mesh", + "data/topography.png" +] +} diff --git a/examples/graphs/minimalsurfacegraph/doc/minimalsurfacegraph.rst b/examples/graphs/minimalsurfacegraph/doc/minimalsurfacegraph.rst deleted file mode 100644 index bfc7a044..00000000 --- a/examples/graphs/minimalsurfacegraph/doc/minimalsurfacegraph.rst +++ /dev/null @@ -1,4 +0,0 @@ -Minimal Surface Example -======================= - -The example shows the minimal code to create a surface. diff --git a/examples/graphs/minimalsurfacegraph/main.py b/examples/graphs/minimalsurfacegraph/main.py deleted file mode 100644 index 5fb4b447..00000000 --- a/examples/graphs/minimalsurfacegraph/main.py +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright (C) 2023 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -import sys - -from PySide6.QtCore import QSize -from PySide6.QtGui import QVector3D -from PySide6.QtGraphs import (Q3DSurface, QSurfaceDataItem, - QSurface3DSeries) -from PySide6.QtWidgets import QApplication -from PySide6.QtQuickWidgets import QQuickWidget - - -DESCRIPTION = """Minimal Qt Graphs Surface Example - -Use the mouse wheel to zoom. Rotate using the right mouse button. -""" - - -if __name__ == '__main__': - app = QApplication(sys.argv) - - print(DESCRIPTION) - - surface = Q3DSurface() - axis = surface.axisX() - axis.setTitle("X") - axis.setTitleVisible(True) - axis = surface.axisY() - axis.setTitle("Y") - axis.setTitleVisible(True) - axis = surface.axisZ() - axis.setTitle("Z") - axis.setTitleVisible(True) - - data = [] - data_row1 = [QSurfaceDataItem(QVector3D(0, 0.1, 0.5)), - QSurfaceDataItem(QVector3D(1, 0.5, 0.5))] - data.append(data_row1) - data_row2 = [QSurfaceDataItem(QVector3D(0, 1.8, 1)), - QSurfaceDataItem(QVector3D(1, 1.2, 1))] - data.append(data_row2) - - series = QSurface3DSeries() - series.dataProxy().resetArray(data) - surface.addSeries(series) - - available_height = app.primaryScreen().availableGeometry().height() - width = available_height * 4 / 5 - surface.resize(QSize(width, width)) - surface.setResizeMode(QQuickWidget.SizeRootObjectToView) - surface.show() - - sys.exit(app.exec()) diff --git a/examples/graphs/widgetgallery/axesinputhandler.py b/examples/graphs/widgetgallery/axesinputhandler.py deleted file mode 100644 index 768fac5f..00000000 --- a/examples/graphs/widgetgallery/axesinputhandler.py +++ /dev/null @@ -1,101 +0,0 @@ -# Copyright (C) 2023 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -from enum import Enum -from math import sin, cos, degrees - -from PySide6.QtCore import Qt -from PySide6.QtGraphs import QAbstract3DGraph, Q3DInputHandler - - -class InputState(Enum): - StateNormal = 0 - StateDraggingX = 1 - StateDraggingZ = 2 - StateDraggingY = 3 - - -class AxesInputHandler(Q3DInputHandler): - - def __init__(self, graph, parent=None): - super().__init__(parent) - self._mousePressed = False - self._state = InputState.StateNormal - self._axisX = None - self._axisZ = None - self._axisY = None - self._speedModifier = 15.0 - - # Connect to the item selection signal from graph - graph.selectedElementChanged.connect(self.handleElementSelected) - - def setAxes(self, axisX, axisZ, axisY): - self._axisX = axisX - self._axisZ = axisZ - self._axisY = axisY - - def setDragSpeedModifier(self, modifier): - self._speedModifier = modifier - - def mousePressEvent(self, event, mousePos): - super().mousePressEvent(event, mousePos) - if Qt.LeftButton == event.button(): - self._mousePressed = True - - def mouseMoveEvent(self, event, mousePos): - # Check if we're trying to drag axis label - if self._mousePressed and self._state != InputState.StateNormal: - self.setPreviousInputPos(self.inputPosition()) - self.setInputPosition(mousePos) - self.handleAxisDragging() - else: - super().mouseMoveEvent(event, mousePos) - - def mouseReleaseEvent(self, event, mousePos): - super().mouseReleaseEvent(event, mousePos) - self._mousePressed = False - self._state = InputState.StateNormal - - def handleElementSelected(self, type): - if type == QAbstract3DGraph.ElementAxisXLabel: - self._state = InputState.StateDraggingX - elif type == QAbstract3DGraph.ElementAxisYLabel: - self._state = InputState.StateDraggingY - elif type == QAbstract3DGraph.ElementAxisZLabel: - self._state = InputState.StateDraggingZ - else: - self._state = InputState.StateNormal - - def handleAxisDragging(self): - distance = 0.0 - # Get scene orientation from active camera - ac = self.scene().activeCamera() - xRotation = ac.xRotation() - yRotation = ac.yRotation() - - # Calculate directional drag multipliers based on rotation - xMulX = cos(degrees(xRotation)) - xMulY = sin(degrees(xRotation)) - zMulX = sin(degrees(xRotation)) - zMulY = cos(degrees(xRotation)) - - # Get the drag amount - move = self.inputPosition() - self.previousInputPos() - - # Flip the effect of y movement if we're viewing from below - yMove = -move.y() if yRotation < 0 else move.y() - - # Adjust axes - if self._state == InputState.StateDraggingX: - distance = (move.x() * xMulX - yMove * xMulY) / self._speedModifier - self._axisX.setRange(self._axisX.min() - distance, - self._axisX.max() - distance) - elif self._state == InputState.StateDraggingZ: - distance = (move.x() * zMulX + yMove * zMulY) / self._speedModifier - self._axisZ.setRange(self._axisZ.min() + distance, - self._axisZ.max() + distance) - elif self._state == InputState.StateDraggingY: - # No need to use adjusted y move here - distance = move.y() / self._speedModifier - self._axisY.setRange(self._axisY.min() + distance, - self._axisY.max() + distance) diff --git a/examples/graphs/widgetgallery/bargraph.py b/examples/graphs/widgetgallery/bargraph.py deleted file mode 100644 index db0dd8e5..00000000 --- a/examples/graphs/widgetgallery/bargraph.py +++ /dev/null @@ -1,272 +0,0 @@ -# Copyright (C) 2023 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -from graphmodifier import GraphModifier - -from PySide6.QtCore import QObject, Qt -from PySide6.QtGui import QFont -from PySide6.QtWidgets import (QButtonGroup, QCheckBox, QComboBox, QFontComboBox, - QLabel, QPushButton, QHBoxLayout, QSizePolicy, - QRadioButton, QSlider, QVBoxLayout, QWidget) -from PySide6.QtQuickWidgets import QQuickWidget -from PySide6.QtGraphs import (QAbstract3DGraph, QAbstract3DSeries, Q3DBars) - - -class BarGraph(QObject): - - def __init__(self, minimum_graph_size, maximum_graph_size): - super().__init__() - self._barsGraph = Q3DBars() - self._barsWidget = QWidget() - hLayout = QHBoxLayout(self._barsWidget) - self._barsGraph.setMinimumSize(minimum_graph_size) - self._barsGraph.setMaximumSize(maximum_graph_size) - self._barsGraph.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) - self._barsGraph.setFocusPolicy(Qt.StrongFocus) - self._barsGraph.setResizeMode(QQuickWidget.SizeRootObjectToView) - hLayout.addWidget(self._barsGraph, 1) - - vLayout = QVBoxLayout() - hLayout.addLayout(vLayout) - - themeList = QComboBox(self._barsWidget) - themeList.addItem("Qt") - themeList.addItem("Primary Colors") - themeList.addItem("Digia") - themeList.addItem("Stone Moss") - themeList.addItem("Army Blue") - themeList.addItem("Retro") - themeList.addItem("Ebony") - themeList.addItem("Isabelle") - themeList.setCurrentIndex(0) - - labelButton = QPushButton(self._barsWidget) - labelButton.setText("Change label style") - - smoothCheckBox = QCheckBox(self._barsWidget) - smoothCheckBox.setText("Smooth bars") - smoothCheckBox.setChecked(False) - - barStyleList = QComboBox(self._barsWidget) - barStyleList.addItem("Bar", QAbstract3DSeries.MeshBar) - barStyleList.addItem("Pyramid", QAbstract3DSeries.MeshPyramid) - barStyleList.addItem("Cone", QAbstract3DSeries.MeshCone) - barStyleList.addItem("Cylinder", QAbstract3DSeries.MeshCylinder) - barStyleList.addItem("Bevel bar", QAbstract3DSeries.MeshBevelBar) - barStyleList.addItem("Sphere", QAbstract3DSeries.MeshSphere) - barStyleList.setCurrentIndex(4) - - cameraButton = QPushButton(self._barsWidget) - cameraButton.setText("Change camera preset") - - zoomToSelectedButton = QPushButton(self._barsWidget) - zoomToSelectedButton.setText("Zoom to selected bar") - - selectionModeList = QComboBox(self._barsWidget) - selectionModeList.addItem("None", QAbstract3DGraph.SelectionNone) - selectionModeList.addItem("Bar", QAbstract3DGraph.SelectionItem) - selectionModeList.addItem("Row", QAbstract3DGraph.SelectionRow) - sel = QAbstract3DGraph.SelectionItemAndRow - selectionModeList.addItem("Bar and Row", sel) - selectionModeList.addItem("Column", QAbstract3DGraph.SelectionColumn) - sel = QAbstract3DGraph.SelectionItemAndColumn - selectionModeList.addItem("Bar and Column", sel) - sel = QAbstract3DGraph.SelectionRowAndColumn - selectionModeList.addItem("Row and Column", sel) - sel = QAbstract3DGraph.SelectionItemRowAndColumn - selectionModeList.addItem("Bar, Row and Column", sel) - sel = QAbstract3DGraph.SelectionSlice | QAbstract3DGraph.SelectionRow - selectionModeList.addItem("Slice into Row", sel) - sel = QAbstract3DGraph.SelectionSlice | QAbstract3DGraph.SelectionItemAndRow - selectionModeList.addItem("Slice into Row and Item", sel) - sel = QAbstract3DGraph.SelectionSlice | QAbstract3DGraph.SelectionColumn - selectionModeList.addItem("Slice into Column", sel) - sel = (QAbstract3DGraph.SelectionSlice - | QAbstract3DGraph.SelectionItemAndColumn) - selectionModeList.addItem("Slice into Column and Item", sel) - sel = (QAbstract3DGraph.SelectionItemRowAndColumn - | QAbstract3DGraph.SelectionMultiSeries) - selectionModeList.addItem("Multi: Bar, Row, Col", sel) - sel = (QAbstract3DGraph.SelectionSlice - | QAbstract3DGraph.SelectionItemAndRow - | QAbstract3DGraph.SelectionMultiSeries) - selectionModeList.addItem("Multi, Slice: Row, Item", sel) - sel = (QAbstract3DGraph.SelectionSlice - | QAbstract3DGraph.SelectionItemAndColumn - | QAbstract3DGraph.SelectionMultiSeries) - selectionModeList.addItem("Multi, Slice: Col, Item", sel) - selectionModeList.setCurrentIndex(1) - - backgroundCheckBox = QCheckBox(self._barsWidget) - backgroundCheckBox.setText("Show background") - backgroundCheckBox.setChecked(False) - - gridCheckBox = QCheckBox(self._barsWidget) - gridCheckBox.setText("Show grid") - gridCheckBox.setChecked(True) - - seriesCheckBox = QCheckBox(self._barsWidget) - seriesCheckBox.setText("Show second series") - seriesCheckBox.setChecked(False) - - reverseValueAxisCheckBox = QCheckBox(self._barsWidget) - reverseValueAxisCheckBox.setText("Reverse value axis") - reverseValueAxisCheckBox.setChecked(False) - - reflectionCheckBox = QCheckBox(self._barsWidget) - reflectionCheckBox.setText("Show reflections") - reflectionCheckBox.setChecked(False) - - rotationSliderX = QSlider(Qt.Horizontal, self._barsWidget) - rotationSliderX.setTickInterval(30) - rotationSliderX.setTickPosition(QSlider.TicksBelow) - rotationSliderX.setMinimum(-180) - rotationSliderX.setValue(0) - rotationSliderX.setMaximum(180) - rotationSliderY = QSlider(Qt.Horizontal, self._barsWidget) - rotationSliderY.setTickInterval(15) - rotationSliderY.setTickPosition(QSlider.TicksAbove) - rotationSliderY.setMinimum(-90) - rotationSliderY.setValue(0) - rotationSliderY.setMaximum(90) - - fontSizeSlider = QSlider(Qt.Horizontal, self._barsWidget) - fontSizeSlider.setTickInterval(10) - fontSizeSlider.setTickPosition(QSlider.TicksBelow) - fontSizeSlider.setMinimum(1) - fontSizeSlider.setValue(30) - fontSizeSlider.setMaximum(100) - - fontList = QFontComboBox(self._barsWidget) - fontList.setCurrentFont(QFont("Times New Roman")) - - shadowQuality = QComboBox(self._barsWidget) - shadowQuality.addItem("None") - shadowQuality.addItem("Low") - shadowQuality.addItem("Medium") - shadowQuality.addItem("High") - shadowQuality.addItem("Low Soft") - shadowQuality.addItem("Medium Soft") - shadowQuality.addItem("High Soft") - shadowQuality.setCurrentIndex(5) - - rangeList = QComboBox(self._barsWidget) - rangeList.addItem("2015") - rangeList.addItem("2016") - rangeList.addItem("2017") - rangeList.addItem("2018") - rangeList.addItem("2019") - rangeList.addItem("2020") - rangeList.addItem("2021") - rangeList.addItem("2022") - rangeList.addItem("All") - rangeList.setCurrentIndex(8) - - axisTitlesVisibleCB = QCheckBox(self._barsWidget) - axisTitlesVisibleCB.setText("Axis titles visible") - axisTitlesVisibleCB.setChecked(True) - - axisTitlesFixedCB = QCheckBox(self._barsWidget) - axisTitlesFixedCB.setText("Axis titles fixed") - axisTitlesFixedCB.setChecked(True) - - axisLabelRotationSlider = QSlider(Qt.Horizontal, self._barsWidget) - axisLabelRotationSlider.setTickInterval(10) - axisLabelRotationSlider.setTickPosition(QSlider.TicksBelow) - axisLabelRotationSlider.setMinimum(0) - axisLabelRotationSlider.setValue(30) - axisLabelRotationSlider.setMaximum(90) - - modeGroup = QButtonGroup(self._barsWidget) - modeWeather = QRadioButton("Temperature Data", self._barsWidget) - modeWeather.setChecked(True) - modeCustomProxy = QRadioButton("Custom Proxy Data", self._barsWidget) - modeGroup.addButton(modeWeather) - modeGroup.addButton(modeCustomProxy) - - vLayout.addWidget(QLabel("Rotate horizontally")) - vLayout.addWidget(rotationSliderX, 0, Qt.AlignTop) - vLayout.addWidget(QLabel("Rotate vertically")) - vLayout.addWidget(rotationSliderY, 0, Qt.AlignTop) - vLayout.addWidget(labelButton, 0, Qt.AlignTop) - vLayout.addWidget(cameraButton, 0, Qt.AlignTop) - vLayout.addWidget(zoomToSelectedButton, 0, Qt.AlignTop) - vLayout.addWidget(backgroundCheckBox) - vLayout.addWidget(gridCheckBox) - vLayout.addWidget(smoothCheckBox) - vLayout.addWidget(reflectionCheckBox) - vLayout.addWidget(seriesCheckBox) - vLayout.addWidget(reverseValueAxisCheckBox) - vLayout.addWidget(axisTitlesVisibleCB) - vLayout.addWidget(axisTitlesFixedCB) - vLayout.addWidget(QLabel("Show year")) - vLayout.addWidget(rangeList) - vLayout.addWidget(QLabel("Change bar style")) - vLayout.addWidget(barStyleList) - vLayout.addWidget(QLabel("Change selection mode")) - vLayout.addWidget(selectionModeList) - vLayout.addWidget(QLabel("Change theme")) - vLayout.addWidget(themeList) - vLayout.addWidget(QLabel("Adjust shadow quality")) - vLayout.addWidget(shadowQuality) - vLayout.addWidget(QLabel("Change font")) - vLayout.addWidget(fontList) - vLayout.addWidget(QLabel("Adjust font size")) - vLayout.addWidget(fontSizeSlider) - vLayout.addWidget(QLabel("Axis label rotation")) - vLayout.addWidget(axisLabelRotationSlider, 0, Qt.AlignTop) - vLayout.addWidget(modeWeather, 0, Qt.AlignTop) - vLayout.addWidget(modeCustomProxy, 1, Qt.AlignTop) - - self._modifier = GraphModifier(self._barsGraph, self) - - rotationSliderX.valueChanged.connect(self._modifier.rotateX) - rotationSliderY.valueChanged.connect(self._modifier.rotateY) - - labelButton.clicked.connect(self._modifier.changeLabelBackground) - cameraButton.clicked.connect(self._modifier.changePresetCamera) - zoomToSelectedButton.clicked.connect(self._modifier.zoomToSelectedBar) - - backgroundCheckBox.stateChanged.connect(self._modifier.setBackgroundEnabled) - gridCheckBox.stateChanged.connect(self._modifier.setGridEnabled) - smoothCheckBox.stateChanged.connect(self._modifier.setSmoothBars) - seriesCheckBox.stateChanged.connect(self._modifier.setSeriesVisibility) - reverseValueAxisCheckBox.stateChanged.connect(self._modifier.setReverseValueAxis) - reflectionCheckBox.stateChanged.connect(self._modifier.setReflection) - - self._modifier.backgroundEnabledChanged.connect(backgroundCheckBox.setChecked) - self._modifier.gridEnabledChanged.connect(gridCheckBox.setChecked) - - rangeList.currentIndexChanged.connect(self._modifier.changeRange) - - barStyleList.currentIndexChanged.connect(self._modifier.changeStyle) - - selectionModeList.currentIndexChanged.connect(self._modifier.changeSelectionMode) - - themeList.currentIndexChanged.connect(self._modifier.changeTheme) - - shadowQuality.currentIndexChanged.connect(self._modifier.changeShadowQuality) - - self._modifier.shadowQualityChanged.connect(shadowQuality.setCurrentIndex) - self._barsGraph.shadowQualityChanged.connect(self._modifier.shadowQualityUpdatedByVisual) - - fontSizeSlider.valueChanged.connect(self._modifier.changeFontSize) - fontList.currentFontChanged.connect(self._modifier.changeFont) - - self._modifier.fontSizeChanged.connect(fontSizeSlider.setValue) - self._modifier.fontChanged.connect(fontList.setCurrentFont) - - axisTitlesVisibleCB.stateChanged.connect(self._modifier.setAxisTitleVisibility) - axisTitlesFixedCB.stateChanged.connect(self._modifier.setAxisTitleFixed) - axisLabelRotationSlider.valueChanged.connect(self._modifier.changeLabelRotation) - - modeWeather.toggled.connect(self._modifier.setDataModeToWeather) - modeCustomProxy.toggled.connect(self._modifier.setDataModeToCustom) - modeWeather.toggled.connect(seriesCheckBox.setEnabled) - modeWeather.toggled.connect(rangeList.setEnabled) - modeWeather.toggled.connect(axisTitlesVisibleCB.setEnabled) - modeWeather.toggled.connect(axisTitlesFixedCB.setEnabled) - modeWeather.toggled.connect(axisLabelRotationSlider.setEnabled) - - def barsWidget(self): - return self._barsWidget diff --git a/examples/graphs/widgetgallery/custominputhandler.py b/examples/graphs/widgetgallery/custominputhandler.py deleted file mode 100644 index 458ef62d..00000000 --- a/examples/graphs/widgetgallery/custominputhandler.py +++ /dev/null @@ -1,177 +0,0 @@ -# Copyright (C) 2023 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -from enum import Enum -from math import sin, cos, degrees - -from PySide6.QtCore import Qt -from PySide6.QtGraphs import (QAbstract3DGraph, Q3DInputHandler) - - -class InputState(Enum): - StateNormal = 0 - StateDraggingX = 1 - StateDraggingZ = 2 - StateDraggingY = 3 - - -class CustomInputHandler(Q3DInputHandler): - - def __init__(self, graph, parent=None): - super().__init__(parent) - self._highlight = None - self._mousePressed = False - self._state = InputState.StateNormal - self._axisX = None - self._axisY = None - self._axisZ = None - self._speedModifier = 20.0 - self._aspectRatio = 0.0 - self._axisXMinValue = 0.0 - self._axisXMaxValue = 0.0 - self._axisXMinRange = 0.0 - self._axisZMinValue = 0.0 - self._axisZMaxValue = 0.0 - self._axisZMinRange = 0.0 - self._areaMinValue = 0.0 - self._areaMaxValue = 0.0 - - # Connect to the item selection signal from graph - graph.selectedElementChanged.connect(self.handleElementSelected) - - def setAspectRatio(self, ratio): - self._aspectRatio = ratio - - def setHighlightSeries(self, series): - self._highlight = series - - def setDragSpeedModifier(self, modifier): - self._speedModifier = modifier - - def setLimits(self, min, max, minRange): - self._areaMinValue = min - self._areaMaxValue = max - self._axisXMinValue = self._areaMinValue - self._axisXMaxValue = self._areaMaxValue - self._axisZMinValue = self._areaMinValue - self._axisZMaxValue = self._areaMaxValue - self._axisXMinRange = minRange - self._axisZMinRange = minRange - - def setAxes(self, axisX, axisY, axisZ): - self._axisX = axisX - self._axisY = axisY - self._axisZ = axisZ - - def mousePressEvent(self, event, mousePos): - if Qt.LeftButton == event.button(): - self._highlight.setVisible(False) - self._mousePressed = True - super().mousePressEvent(event, mousePos) - - def wheelEvent(self, event): - delta = float(event.angleDelta().y()) - - self._axisXMinValue += delta - self._axisXMaxValue -= delta - self._axisZMinValue += delta - self._axisZMaxValue -= delta - self.checkConstraints() - - y = (self._axisXMaxValue - self._axisXMinValue) * self._aspectRatio - - self._axisX.setRange(self._axisXMinValue, self._axisXMaxValue) - self._axisY.setRange(100.0, y) - self._axisZ.setRange(self._axisZMinValue, self._axisZMaxValue) - - def mouseMoveEvent(self, event, mousePos): - # Check if we're trying to drag axis label - if self._mousePressed and self._state != InputState.StateNormal: - self.setPreviousInputPos(self.inputPosition()) - self.setInputPosition(mousePos) - self.handleAxisDragging() - else: - super().mouseMoveEvent(event, mousePos) - - def mouseReleaseEvent(self, event, mousePos): - super().mouseReleaseEvent(event, mousePos) - self._mousePressed = False - self._state = InputState.StateNormal - - def handleElementSelected(self, type): - if type == QAbstract3DGraph.ElementAxisXLabel: - self._state = InputState.StateDraggingX - elif type == QAbstract3DGraph.ElementAxisZLabel: - self._state = InputState.StateDraggingZ - else: - self._state = InputState.StateNormal - - def handleAxisDragging(self): - distance = 0.0 - - # Get scene orientation from active camera - xRotation = self.scene().activeCamera().xRotation() - - # Calculate directional drag multipliers based on rotation - xMulX = cos(degrees(xRotation)) - xMulY = sin(degrees(xRotation)) - zMulX = xMulY - zMulY = xMulX - - # Get the drag amount - move = self.inputPosition() - self.previousInputPos() - - # Adjust axes - if self._state == InputState.StateDraggingX: - distance = (move.x() * xMulX - move.y() * xMulY) * self._speedModifier - self._axisXMinValue -= distance - self._axisXMaxValue -= distance - if self._axisXMinValue < self._areaMinValue: - dist = self._axisXMaxValue - self._axisXMinValue - self._axisXMinValue = self._areaMinValue - self._axisXMaxValue = self._axisXMinValue + dist - - if self._axisXMaxValue > self._areaMaxValue: - dist = self._axisXMaxValue - self._axisXMinValue - self._axisXMaxValue = self._areaMaxValue - self._axisXMinValue = self._axisXMaxValue - dist - - self._axisX.setRange(self._axisXMinValue, self._axisXMaxValue) - elif self._state == InputState.StateDraggingZ: - distance = (move.x() * zMulX + move.y() * zMulY) * self._speedModifier - self._axisZMinValue += distance - self._axisZMaxValue += distance - if self._axisZMinValue < self._areaMinValue: - dist = self._axisZMaxValue - self._axisZMinValue - self._axisZMinValue = self._areaMinValue - self._axisZMaxValue = self._axisZMinValue + dist - - if self._axisZMaxValue > self._areaMaxValue: - dist = self._axisZMaxValue - self._axisZMinValue - self._axisZMaxValue = self._areaMaxValue - self._axisZMinValue = self._axisZMaxValue - dist - - self._axisZ.setRange(self._axisZMinValue, self._axisZMaxValue) - - def checkConstraints(self): - if self._axisXMinValue < self._areaMinValue: - self._axisXMinValue = self._areaMinValue - if self._axisXMaxValue > self._areaMaxValue: - self._axisXMaxValue = self._areaMaxValue - # Don't allow too much zoom in - range = self._axisXMaxValue - self._axisXMinValue - if range < self._axisXMinRange: - adjust = (self._axisXMinRange - range) / 2.0 - self._axisXMinValue -= adjust - self._axisXMaxValue += adjust - - if self._axisZMinValue < self._areaMinValue: - self._axisZMinValue = self._areaMinValue - if self._axisZMaxValue > self._areaMaxValue: - self._axisZMaxValue = self._areaMaxValue - # Don't allow too much zoom in - range = self._axisZMaxValue - self._axisZMinValue - if range < self._axisZMinRange: - adjust = (self._axisZMinRange - range) / 2.0 - self._axisZMinValue -= adjust - self._axisZMaxValue += adjust diff --git a/examples/graphs/widgetgallery/data/layer_1.png b/examples/graphs/widgetgallery/data/layer_1.png deleted file mode 100644 index 9138c710..00000000 Binary files a/examples/graphs/widgetgallery/data/layer_1.png and /dev/null differ diff --git a/examples/graphs/widgetgallery/data/layer_2.png b/examples/graphs/widgetgallery/data/layer_2.png deleted file mode 100644 index 61631ae8..00000000 Binary files a/examples/graphs/widgetgallery/data/layer_2.png and /dev/null differ diff --git a/examples/graphs/widgetgallery/data/layer_3.png b/examples/graphs/widgetgallery/data/layer_3.png deleted file mode 100644 index 066ffbe7..00000000 Binary files a/examples/graphs/widgetgallery/data/layer_3.png and /dev/null differ diff --git a/examples/graphs/widgetgallery/data/license.txt b/examples/graphs/widgetgallery/data/license.txt deleted file mode 100644 index 749daf31..00000000 --- a/examples/graphs/widgetgallery/data/license.txt +++ /dev/null @@ -1,77 +0,0 @@ -License information regarding the data obtained from National Land Survey of -Finland http://www.maanmittauslaitos.fi/en -- topographic model from Elevation model 2 m (U4421B, U4421D, U4422A and - U4422C) 08/2014 -- map image extracted from Topographic map raster 1:50 000 (U442) 08/2014 - -National Land Survey open data licence - version 1.0 - 1 May 2012 - -1. General information - -The National Land Survey of Finland (hereinafter the Licensor), as the holder -of the immaterial rights to the data, has granted on the terms mentioned below -the right to use a copy (hereinafter data or dataset(s)) of the data (or a part -of it). - -The Licensee is a natural or legal person who makes use of the data covered by -this licence. The Licensee accepts the terms of this licence by receiving the -dataset(s) covered by the licence. - -This Licence agreement does not create a co-operation or business relationship -between the Licensee and the Licensor. - -2. Terms of the licence - -2.1. Right of use - -This licence grants a worldwide, free of charge and irrevocable parallel right -of use to open data. According to the terms of the licence, data received by -the Licensee can be freely: - - copied, distributed and published, - - modified and utilised commercially and non-commercially, - - inserted into other products and - - used as a part of a software application or service. - -2.2. Duties and responsibilities of the Licensee - -Through reasonable means suitable to the distribution medium or method which is -used in conjunction with a product containing data or a service utilising data -covered by this licence or while distributing data, the Licensee shall: - - mention the name of the Licensor, the name of the dataset(s) and the time - when the National Land Survey has delivered the dataset(s) (e.g.: contains - data from the National Land Survey of Finland Topographic Database 06/2012) - - provide a copy of this licence or a link to it, as well as - - require third parties to provide the same information when granting rights - to copies of dataset(s) or products and services containing such data and - - remove the name of the Licensor from the product or service, if required to - do so by the Licensor. - -The terms of this licence do not allow the Licensee to state in conjunction -with the use of dataset(s) that the Licensor supports or recommends such use. - -2.3. Duties and responsibilities of the Licensor - -The Licensor shall ensure that - - the Licensor has the right to grant rights to the dataset(s) in accordance - with this licence. - -The data has been licensed "as is" and the Licensor - - shall not be held responsible for any errors or omissions in the data, - disclaims any warranty for the validity or up to date status of the data and - shall be free from liability for direct or consequential damages arising - from the use of data provided by the Licensor, - - and is not obligated to ensure the continuous availability of the data, nor - to announce in advance the interruption or cessation of availability, and - the Licensor shall be free from liability for direct or consequential - damages arising from any such interruption or cessation. - -3. Jurisdiction - -Finnish law shall apply to this licence. - -4. Changes to this licence - -The Licensor may at any time change the terms of the licence or apply a -different licence to the data. The terms of this licence shall, however, still -apply to such data that has been received prior to the change of the terms of -the licence or the licence itself. diff --git a/examples/graphs/widgetgallery/data/maptexture.jpg b/examples/graphs/widgetgallery/data/maptexture.jpg deleted file mode 100644 index ae5d66eb..00000000 Binary files a/examples/graphs/widgetgallery/data/maptexture.jpg and /dev/null differ diff --git a/examples/graphs/widgetgallery/data/narrowarrow.mesh b/examples/graphs/widgetgallery/data/narrowarrow.mesh deleted file mode 100644 index 288867b1..00000000 Binary files a/examples/graphs/widgetgallery/data/narrowarrow.mesh and /dev/null differ diff --git a/examples/graphs/widgetgallery/data/oilrig.mesh b/examples/graphs/widgetgallery/data/oilrig.mesh deleted file mode 100644 index 4a7baedd..00000000 Binary files a/examples/graphs/widgetgallery/data/oilrig.mesh and /dev/null differ diff --git a/examples/graphs/widgetgallery/data/pipe.mesh b/examples/graphs/widgetgallery/data/pipe.mesh deleted file mode 100644 index 984b6d44..00000000 Binary files a/examples/graphs/widgetgallery/data/pipe.mesh and /dev/null differ diff --git a/examples/graphs/widgetgallery/data/raindata.txt b/examples/graphs/widgetgallery/data/raindata.txt deleted file mode 100644 index d9558921..00000000 --- a/examples/graphs/widgetgallery/data/raindata.txt +++ /dev/null @@ -1,158 +0,0 @@ -# Rainfall per month from 2010 to 2022 in Northern Finland (Oulu) -# Format: year, month, rainfall -2010,1, 0, -2010,2, 3.4, -2010,3, 52, -2010,4, 33.8, -2010,5, 45.6, -2010,6, 43.8, -2010,7, 104.6, -2010,8, 105.4, -2010,9, 107.2, -2010,10,38.6, -2010,11,17.8, -2010,12,0, -2011,1, 8.2, -2011,2, 1.6, -2011,3, 27.4, -2011,4, 15.8, -2011,5, 57.6, -2011,6, 85.2, -2011,7, 127, -2011,8, 72.2, -2011,9, 82.2, -2011,10,62.4, -2011,11,31.6, -2011,12,53.8, -2012,1, 0, -2012,2, 5, -2012,3, 32.4, -2012,4, 57.6, -2012,5, 71.4, -2012,6, 60.8, -2012,7, 109, -2012,8, 43.6, -2012,9, 79.4, -2012,10,117.2, -2012,11,59, -2012,12,0.2, -2013,1, 28, -2013,2, 19, -2013,3, 0, -2013,4, 37.6, -2013,5, 44.2, -2013,6, 104.8, -2013,7, 84.2, -2013,8, 57.2, -2013,9, 37.2, -2013,10,64.6, -2013,11,77.8, -2013,12,92.8, -2014,1, 23.8, -2014,2, 23.6, -2014,3, 15.4, -2014,4, 13.2, -2014,5, 36.4, -2014,6, 26.4, -2014,7, 95.8, -2014,8, 81.8, -2014,9, 13.8, -2014,10,94.6, -2014,11,44.6, -2014,12,31, -2015,1, 37.4, -2015,2, 21, -2015,3, 42, -2015,4, 8.8, -2015,5, 82.4, -2015,6, 150, -2015,7, 56.8, -2015,8, 67.2, -2015,9, 131.2, -2015,10,38.4, -2015,11,83.4, -2015,12,47.8, -2016,1, 12.4, -2016,2, 34.8, -2016,3, 29, -2016,4, 40.4, -2016,5, 32.4, -2016,6, 80.2, -2016,7, 102.6, -2016,8, 95.6, -2016,9, 40.2, -2016,10,7.8, -2016,11,39.6, -2016,12,8.8, -2017,1, 9.4, -2017,2, 6.6, -2017,3, 29, -2017,4, 46.2, -2017,5, 43.2, -2017,6, 25.2, -2017,7, 72.4, -2017,8, 58.8, -2017,9, 68.8, -2017,10,45.8, -2017,11,36.8, -2017,12,29.6, -2018,1, 19.8, -2018,2, 0.8, -2018,3, 4, -2018,4, 23.2, -2018,5, 13.2, -2018,6, 62.8, -2018,7, 33, -2018,8, 96.6, -2018,9, 72.6, -2018,10,48.8, -2018,11,31.8, -2018,12,12.8, -2019,1, 0.2, -2019,2, 24.8, -2019,3, 32, -2019,4, 8.8, -2019,5, 71.4, -2019,6, 65.8, -2019,7, 17.6, -2019,8, 90, -2019,9, 50, -2019,10,77, -2019,11,27, -2019,12,43.2, -2020,1, 28.8, -2020,2, 45, -2020,3, 18.6, -2020,4, 13, -2020,5, 30.8, -2020,6, 21.4, -2020,7, 163.6, -2020,8, 12, -2020,9, 102.4, -2020,10,133.2, -2020,11,69.8, -2020,12,40.6, -2021,1, 0.4, -2021,2, 21.6, -2021,3, 24, -2021,4, 51.4, -2021,5, 76.4, -2021,6, 29.2, -2021,7, 36.4, -2021,8, 116, -2021,9, 72.4, -2021,10,93.4, -2021,11,21, -2021,12,10.2, -2022,1, 8.6, -2022,2, 6.6, -2022,3, 5.2, -2022,4, 15.2, -2022,5, 37.6, -2022,6, 45, -2022,7, 67.4, -2022,8, 161.6, -2022,9, 22.8, -2022,10,75.2, -2022,11,21.8, -2022,12,0.2 diff --git a/examples/graphs/widgetgallery/data/refinery.mesh b/examples/graphs/widgetgallery/data/refinery.mesh deleted file mode 100644 index a7e24935..00000000 Binary files a/examples/graphs/widgetgallery/data/refinery.mesh and /dev/null differ diff --git a/examples/graphs/widgetgallery/data/topography.png b/examples/graphs/widgetgallery/data/topography.png deleted file mode 100644 index 9349cdb3..00000000 Binary files a/examples/graphs/widgetgallery/data/topography.png and /dev/null differ diff --git a/examples/graphs/widgetgallery/doc/widgetgallery.rst b/examples/graphs/widgetgallery/doc/widgetgallery.rst deleted file mode 100644 index 1470001d..00000000 --- a/examples/graphs/widgetgallery/doc/widgetgallery.rst +++ /dev/null @@ -1,11 +0,0 @@ -Widget Gallery -============== - - -Widget Gallery demonstrates all three graph types and some of their special -features. The graphs have their own tabs in the application. - - -.. image:: widgetgallery.webp - :width: 400 - :alt: Widget Screenshot diff --git a/examples/graphs/widgetgallery/doc/widgetgallery.webp b/examples/graphs/widgetgallery/doc/widgetgallery.webp deleted file mode 100644 index eb576726..00000000 Binary files a/examples/graphs/widgetgallery/doc/widgetgallery.webp and /dev/null differ diff --git a/examples/graphs/widgetgallery/graphmodifier.py b/examples/graphs/widgetgallery/graphmodifier.py deleted file mode 100644 index 938e09cf..00000000 --- a/examples/graphs/widgetgallery/graphmodifier.py +++ /dev/null @@ -1,394 +0,0 @@ -# Copyright (C) 2023 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - - -from math import atan, degrees -import numpy as np - -from PySide6.QtCore import QObject, QPropertyAnimation, Signal, Slot -from PySide6.QtGui import QFont, QVector3D -from PySide6.QtGraphs import (QAbstract3DGraph, QAbstract3DSeries, - QBarDataItem, QBar3DSeries, QCategory3DAxis, - QValue3DAxis, Q3DCamera, Q3DTheme) - -from rainfalldata import RainfallData - -# Set up data -TEMP_OULU = np.array([ - [-7.4, -2.4, 0.0, 3.0, 8.2, 11.6, 14.7, 15.4, 11.4, 4.2, 2.1, -2.3], # 2015 - [-13.4, -3.9, -1.8, 3.1, 10.6, 13.7, 17.8, 13.6, 10.7, 3.5, -3.1, -4.2], # 2016 - [-5.7, -6.7, -3.0, -0.1, 4.7, 12.4, 16.1, 14.1, 9.4, 3.0, -0.3, -3.2], # 2017 - [-6.4, -11.9, -7.4, 1.9, 11.4, 12.4, 21.5, 16.1, 11.0, 4.4, 2.1, -4.1], # 2018 - [-11.7, -6.1, -2.4, 3.9, 7.2, 14.5, 15.6, 14.4, 8.5, 2.0, -3.0, -1.5], # 2019 - [-2.1, -3.4, -1.8, 0.6, 7.0, 17.1, 15.6, 15.4, 11.1, 5.6, 1.9, -1.7], # 2020 - [-9.6, -11.6, -3.2, 2.4, 7.8, 17.3, 19.4, 14.2, 8.0, 5.2, -2.2, -8.6], # 2021 - [-7.3, -6.4, -1.8, 1.3, 8.1, 15.5, 17.6, 17.6, 9.1, 5.4, -1.5, -4.4]], # 2022 - np.float64) - - -TEMP_HELSINKI = np.array([ - [-2.0, -0.1, 1.8, 5.1, 9.7, 13.7, 16.3, 17.3, 12.7, 5.4, 4.6, 2.1], # 2015 - [-10.3, -0.6, 0.0, 4.9, 14.3, 15.7, 17.7, 16.0, 12.7, 4.6, -1.0, -0.9], # 2016 - [-2.9, -3.3, 0.7, 2.3, 9.9, 13.8, 16.1, 15.9, 11.4, 5.0, 2.7, 0.7], # 2017 - [-2.2, -8.4, -4.7, 5.0, 15.3, 15.8, 21.2, 18.2, 13.3, 6.7, 2.8, -2.0], # 2018 - [-6.2, -0.5, -0.3, 6.8, 10.6, 17.9, 17.5, 16.8, 11.3, 5.2, 1.8, 1.4], # 2019 - [1.9, 0.5, 1.7, 4.5, 9.5, 18.4, 16.5, 16.8, 13.0, 8.2, 4.4, 0.9], # 2020 - [-4.7, -8.1, -0.9, 4.5, 10.4, 19.2, 20.9, 15.4, 9.5, 8.0, 1.5, -6.7], # 2021 - [-3.3, -2.2, -0.2, 3.3, 9.6, 16.9, 18.1, 18.9, 9.2, 7.6, 2.3, -3.4]], # 2022 - np.float64) - - -class GraphModifier(QObject): - - shadowQualityChanged = Signal(int) - backgroundEnabledChanged = Signal(bool) - gridEnabledChanged = Signal(bool) - fontChanged = Signal(QFont) - fontSizeChanged = Signal(int) - - def __init__(self, bargraph, parent): - super().__init__(parent) - self._graph = bargraph - self._temperatureAxis = QValue3DAxis() - self._yearAxis = QCategory3DAxis() - self._monthAxis = QCategory3DAxis() - self._primarySeries = QBar3DSeries() - self._secondarySeries = QBar3DSeries() - self._celsiusString = "°C" - - self._xRotation = float(0) - self._yRotation = float(0) - self._fontSize = 30 - self._segments = 4 - self._subSegments = 3 - self._minval = float(-20) - self._maxval = float(20) - self._barMesh = QAbstract3DSeries.MeshBevelBar - self._smooth = False - self._animationCameraX = QPropertyAnimation() - self._animationCameraY = QPropertyAnimation() - self._animationCameraZoom = QPropertyAnimation() - self._animationCameraTarget = QPropertyAnimation() - self._defaultAngleX = float(0) - self._defaultAngleY = float(0) - self._defaultZoom = float(0) - self._defaultTarget = [] - self._customData = None - - self._graph.setShadowQuality(QAbstract3DGraph.ShadowQualitySoftMedium) - theme = self._graph.activeTheme() - theme.setBackgroundEnabled(False) - theme.setFont(QFont("Times New Roman", self._fontSize)) - theme.setLabelBackgroundEnabled(True) - self._graph.setMultiSeriesUniform(True) - - self._months = ["January", "February", "March", "April", "May", "June", - "July", "August", "September", "October", "November", - "December"] - self._years = ["2015", "2016", "2017", "2018", "2019", "2020", - "2021", "2022"] - - self._temperatureAxis.setTitle("Average temperature") - self._temperatureAxis.setSegmentCount(self._segments) - self._temperatureAxis.setSubSegmentCount(self._subSegments) - self._temperatureAxis.setRange(self._minval, self._maxval) - self._temperatureAxis.setLabelFormat("%.1f " + self._celsiusString) - self._temperatureAxis.setLabelAutoRotation(30.0) - self._temperatureAxis.setTitleVisible(True) - - self._yearAxis.setTitle("Year") - self._yearAxis.setLabelAutoRotation(30.0) - self._yearAxis.setTitleVisible(True) - self._monthAxis.setTitle("Month") - self._monthAxis.setLabelAutoRotation(30.0) - self._monthAxis.setTitleVisible(True) - - self._graph.setValueAxis(self._temperatureAxis) - self._graph.setRowAxis(self._yearAxis) - self._graph.setColumnAxis(self._monthAxis) - - format = "Oulu - @colLabel @rowLabel: @valueLabel" - self._primarySeries.setItemLabelFormat(format) - self._primarySeries.setMesh(QAbstract3DSeries.MeshBevelBar) - self._primarySeries.setMeshSmooth(False) - - format = "Helsinki - @colLabel @rowLabel: @valueLabel" - self._secondarySeries.setItemLabelFormat(format) - self._secondarySeries.setMesh(QAbstract3DSeries.MeshBevelBar) - self._secondarySeries.setMeshSmooth(False) - self._secondarySeries.setVisible(False) - - self._graph.addSeries(self._primarySeries) - self._graph.addSeries(self._secondarySeries) - - self.changePresetCamera() - - self.resetTemperatureData() - - # Set up property animations for zooming to the selected bar - camera = self._graph.scene().activeCamera() - self._defaultAngleX = camera.xRotation() - self._defaultAngleY = camera.yRotation() - self._defaultZoom = camera.zoomLevel() - self._defaultTarget = camera.target() - - self._animationCameraX.setTargetObject(camera) - self._animationCameraY.setTargetObject(camera) - self._animationCameraZoom.setTargetObject(camera) - self._animationCameraTarget.setTargetObject(camera) - - self._animationCameraX.setPropertyName(b"xRotation") - self._animationCameraY.setPropertyName(b"yRotation") - self._animationCameraZoom.setPropertyName(b"zoomLevel") - self._animationCameraTarget.setPropertyName(b"target") - - duration = 1700 - self._animationCameraX.setDuration(duration) - self._animationCameraY.setDuration(duration) - self._animationCameraZoom.setDuration(duration) - self._animationCameraTarget.setDuration(duration) - - # The zoom always first zooms out above the graph and then zooms in - zoomOutFraction = 0.3 - self._animationCameraX.setKeyValueAt(zoomOutFraction, 0.0) - self._animationCameraY.setKeyValueAt(zoomOutFraction, 90.0) - self._animationCameraZoom.setKeyValueAt(zoomOutFraction, 50.0) - self._animationCameraTarget.setKeyValueAt(zoomOutFraction, - QVector3D(0, 0, 0)) - self._customData = RainfallData() - - def resetTemperatureData(self): - # Create data arrays - dataSet = [] - dataSet2 = [] - - for year in range(0, len(self._years)): - # Create a data row - dataRow = [] - dataRow2 = [] - for month in range(0, len(self._months)): - # Add data to the row - item = QBarDataItem() - item.setValue(TEMP_OULU[year][month]) - dataRow.append(item) - item = QBarDataItem() - item.setValue(TEMP_HELSINKI[year][month]) - dataRow2.append(item) - - # Add the row to the set - dataSet.append(dataRow) - dataSet2.append(dataRow2) - - # Add data to the data proxy (the data proxy assumes ownership of it) - self._primarySeries.dataProxy().resetArray(dataSet, self._years, self._months) - self._secondarySeries.dataProxy().resetArray(dataSet2, self._years, self._months) - - @Slot(int) - def changeRange(self, range): - if range >= len(self._years): - self._yearAxis.setRange(0, len(self._years) - 1) - else: - self._yearAxis.setRange(range, range) - - @Slot(int) - def changeStyle(self, style): - comboBox = self.sender() - if comboBox: - self._barMesh = comboBox.itemData(style) - self._primarySeries.setMesh(self._barMesh) - self._secondarySeries.setMesh(self._barMesh) - self._customData.customSeries().setMesh(self._barMesh) - - def changePresetCamera(self): - self._animationCameraX.stop() - self._animationCameraY.stop() - self._animationCameraZoom.stop() - self._animationCameraTarget.stop() - - # Restore camera target in case animation has changed it - self._graph.scene().activeCamera().setTarget(QVector3D(0.0, 0.0, 0.0)) - - self._preset = Q3DCamera.CameraPresetFront.value - - camera = self._graph.scene().activeCamera() - camera.setCameraPreset(Q3DCamera.CameraPreset(self._preset)) - - self._preset += 1 - if self._preset > Q3DCamera.CameraPresetDirectlyBelow.value: - self._preset = Q3DCamera.CameraPresetFrontLow.value - - @Slot(int) - def changeTheme(self, theme): - currentTheme = self._graph.activeTheme() - currentTheme.setType(Q3DTheme.Theme(theme)) - self.backgroundEnabledChanged.emit(currentTheme.isBackgroundEnabled()) - self.gridEnabledChanged.emit(currentTheme.isGridEnabled()) - self.fontChanged.emit(currentTheme.font()) - self.fontSizeChanged.emit(currentTheme.font().pointSize()) - - def changeLabelBackground(self): - theme = self._graph.activeTheme() - theme.setLabelBackgroundEnabled(not theme.isLabelBackgroundEnabled()) - - @Slot(int) - def changeSelectionMode(self, selectionMode): - comboBox = self.sender() - if comboBox: - flags = comboBox.itemData(selectionMode) - self._graph.setSelectionMode(QAbstract3DGraph.SelectionFlags(flags)) - - def changeFont(self, font): - newFont = font - self._graph.activeTheme().setFont(newFont) - - def changeFontSize(self, fontsize): - self._fontSize = fontsize - font = self._graph.activeTheme().font() - font.setPointSize(self._fontSize) - self._graph.activeTheme().setFont(font) - - @Slot(QAbstract3DGraph.ShadowQuality) - def shadowQualityUpdatedByVisual(self, sq): - # Updates the UI component to show correct shadow quality - self.shadowQualityChanged.emit(sq.value) - - @Slot(int) - def changeLabelRotation(self, rotation): - self._temperatureAxis.setLabelAutoRotation(float(rotation)) - self._monthAxis.setLabelAutoRotation(float(rotation)) - self._yearAxis.setLabelAutoRotation(float(rotation)) - - @Slot(bool) - def setAxisTitleVisibility(self, enabled): - self._temperatureAxis.setTitleVisible(enabled) - self._monthAxis.setTitleVisible(enabled) - self._yearAxis.setTitleVisible(enabled) - - @Slot(bool) - def setAxisTitleFixed(self, enabled): - self._temperatureAxis.setTitleFixed(enabled) - self._monthAxis.setTitleFixed(enabled) - self._yearAxis.setTitleFixed(enabled) - - @Slot() - def zoomToSelectedBar(self): - self._animationCameraX.stop() - self._animationCameraY.stop() - self._animationCameraZoom.stop() - self._animationCameraTarget.stop() - - camera = self._graph.scene().activeCamera() - currentX = camera.xRotation() - currentY = camera.yRotation() - currentZoom = camera.zoomLevel() - currentTarget = camera.target() - - self._animationCameraX.setStartValue(currentX) - self._animationCameraY.setStartValue(currentY) - self._animationCameraZoom.setStartValue(currentZoom) - self._animationCameraTarget.setStartValue(currentTarget) - - selectedBar = (self._graph.selectedSeries().selectedBar() - if self._graph.selectedSeries() - else QBar3DSeries.invalidSelectionPosition()) - - if selectedBar != QBar3DSeries.invalidSelectionPosition(): - # Normalize selected bar position within axis range to determine - # target coordinates - endTarget = QVector3D() - xMin = self._graph.columnAxis().min() - xRange = self._graph.columnAxis().max() - xMin - zMin = self._graph.rowAxis().min() - zRange = self._graph.rowAxis().max() - zMin - endTarget.setX((selectedBar.y() - xMin) / xRange * 2.0 - 1.0) - endTarget.setZ((selectedBar.x() - zMin) / zRange * 2.0 - 1.0) - - # Rotate the camera so that it always points approximately to the - # graph center - endAngleX = 90.0 - degrees(atan(float(endTarget.z() / endTarget.x()))) - if endTarget.x() > 0.0: - endAngleX -= 180.0 - proxy = self._graph.selectedSeries().dataProxy() - barValue = proxy.itemAt(selectedBar.x(), selectedBar.y()).value() - endAngleY = 30.0 if barValue >= 0.0 else -30.0 - if self._graph.valueAxis().reversed(): - endAngleY *= -1.0 - - self._animationCameraX.setEndValue(float(endAngleX)) - self._animationCameraY.setEndValue(endAngleY) - self._animationCameraZoom.setEndValue(250) - self._animationCameraTarget.setEndValue(endTarget) - else: - # No selected bar, so return to the default view - self._animationCameraX.setEndValue(self._defaultAngleX) - self._animationCameraY.setEndValue(self._defaultAngleY) - self._animationCameraZoom.setEndValue(self._defaultZoom) - self._animationCameraTarget.setEndValue(self._defaultTarget) - - self._animationCameraX.start() - self._animationCameraY.start() - self._animationCameraZoom.start() - self._animationCameraTarget.start() - - @Slot(bool) - def setDataModeToWeather(self, enabled): - if enabled: - self.changeDataMode(False) - - @Slot(bool) - def setDataModeToCustom(self, enabled): - if enabled: - self.changeDataMode(True) - - def changeShadowQuality(self, quality): - sq = QAbstract3DGraph.ShadowQuality(quality) - self._graph.setShadowQuality(sq) - self.shadowQualityChanged.emit(quality) - - def rotateX(self, rotation): - self._xRotation = rotation - camera = self._graph.scene().activeCamera() - camera.setCameraPosition(self._xRotation, self._yRotation) - - def rotateY(self, rotation): - self._yRotation = rotation - camera = self._graph.scene().activeCamera() - camera.setCameraPosition(self._xRotation, self._yRotation) - - def setBackgroundEnabled(self, enabled): - self._graph.activeTheme().setBackgroundEnabled(bool(enabled)) - - def setGridEnabled(self, enabled): - self._graph.activeTheme().setGridEnabled(bool(enabled)) - - def setSmoothBars(self, smooth): - self._smooth = bool(smooth) - self._primarySeries.setMeshSmooth(self._smooth) - self._secondarySeries.setMeshSmooth(self._smooth) - self._customData.customSeries().setMeshSmooth(self._smooth) - - def setSeriesVisibility(self, enabled): - self._secondarySeries.setVisible(bool(enabled)) - - def setReverseValueAxis(self, enabled): - self._graph.valueAxis().setReversed(enabled) - - def setReflection(self, enabled): - self._graph.setReflection(enabled) - - def changeDataMode(self, customData): - # Change between weather data and data from custom proxy - if customData: - self._graph.removeSeries(self._primarySeries) - self._graph.removeSeries(self._secondarySeries) - self._graph.addSeries(self._customData.customSeries()) - self._graph.setValueAxis(self._customData.valueAxis()) - self._graph.setRowAxis(self._customData.rowAxis()) - self._graph.setColumnAxis(self._customData.colAxis()) - else: - self._graph.removeSeries(self._customData.customSeries()) - self._graph.addSeries(self._primarySeries) - self._graph.addSeries(self._secondarySeries) - self._graph.setValueAxis(self._temperatureAxis) - self._graph.setRowAxis(self._yearAxis) - self._graph.setColumnAxis(self._monthAxis) diff --git a/examples/graphs/widgetgallery/highlightseries.py b/examples/graphs/widgetgallery/highlightseries.py deleted file mode 100644 index 93ef3a75..00000000 --- a/examples/graphs/widgetgallery/highlightseries.py +++ /dev/null @@ -1,94 +0,0 @@ -# Copyright (C) 2023 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -from PySide6.QtCore import QPoint, Qt, Slot -from PySide6.QtGui import QLinearGradient, QVector3D -from PySide6.QtGraphs import (QSurface3DSeries, QSurfaceDataItem, Q3DTheme) - - -DARK_RED_POS = 1.0 -RED_POS = 0.8 -YELLOW_POS = 0.6 -GREEN_POS = 0.4 -DARK_GREEN_POS = 0.2 - - -class HighlightSeries(QSurface3DSeries): - - def __init__(self): - super().__init__() - self._width = 100 - self._height = 100 - self._srcWidth = 0 - self._srcHeight = 0 - self._position = {} - self._topographicSeries = None - self._minHeight = 0.0 - self.setDrawMode(QSurface3DSeries.DrawSurface) - self.setFlatShadingEnabled(True) - self.setVisible(False) - - def setTopographicSeries(self, series): - self._topographicSeries = series - array = self._topographicSeries.dataProxy().array() - self._srcWidth = len(array[0]) - self._srcHeight = len(array) - self._topographicSeries.selectedPointChanged.connect(self.handlePositionChange) - - def setMinHeight(self, height): - self. m_minHeight = height - - @Slot(QPoint) - def handlePositionChange(self, position): - self._position = position - - if position == self.invalidSelectionPosition(): - self.setVisible(False) - return - - halfWidth = self._width / 2 - halfHeight = self._height / 2 - - startX = position.y() - halfWidth - if startX < 0: - startX = 0 - endX = position.y() + halfWidth - if endX > (self._srcWidth - 1): - endX = self._srcWidth - 1 - startZ = position.x() - halfHeight - if startZ < 0: - startZ = 0 - endZ = position.x() + halfHeight - if endZ > (self._srcHeight - 1): - endZ = self._srcHeight - 1 - - srcProxy = self._topographicSeries.dataProxy() - srcArray = srcProxy.array() - - dataArray = [] - for i in range(int(startZ), int(endZ)): - newRow = [] - srcRow = srcArray[i] - for j in range(startX, endX): - pos = srcRow.at(j).position() - pos.setY(pos.y() + 0.1) - item = QSurfaceDataItem(QVector3D(pos)) - newRow.append(item) - dataArray.append(newRow) - self.dataProxy().resetArray(dataArray) - self.setVisible(True) - - @Slot(float) - def handleGradientChange(self, value): - ratio = self._minHeight / value - - gr = QLinearGradient() - gr.setColorAt(0.0, Qt.black) - gr.setColorAt(DARK_GREEN_POS * ratio, Qt.darkGreen) - gr.setColorAt(GREEN_POS * ratio, Qt.green) - gr.setColorAt(YELLOW_POS * ratio, Qt.yellow) - gr.setColorAt(RED_POS * ratio, Qt.red) - gr.setColorAt(DARK_RED_POS * ratio, Qt.darkRed) - - self.setBaseGradient(gr) - self.setColorStyle(Q3DTheme.ColorStyleRangeGradient) diff --git a/examples/graphs/widgetgallery/main.py b/examples/graphs/widgetgallery/main.py deleted file mode 100644 index 7bb2238a..00000000 --- a/examples/graphs/widgetgallery/main.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright (C) 2023 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -"""PySide6 port of the Qt Graphs widgetgallery example from Qt v6.x""" - -import sys - -from PySide6.QtCore import QSize -from PySide6.QtWidgets import QApplication, QTabWidget - -from bargraph import BarGraph -from scattergraph import ScatterGraph -from surfacegraph import SurfaceGraph - - -if __name__ == "__main__": - app = QApplication(sys.argv) - - # Create a tab widget for creating own tabs for Q3DBars, Q3DScatter, and Q3DSurface - tabWidget = QTabWidget() - tabWidget.setWindowTitle("Widget Gallery") - - screen_size = tabWidget.screen().size() - minimum_graph_size = QSize(screen_size.width() / 2, screen_size.height() / 1.75) - - # Create bar graph - bars = BarGraph(minimum_graph_size, screen_size) - # Create scatter graph - scatter = ScatterGraph(minimum_graph_size, screen_size) - # Create surface graph - surface = SurfaceGraph(minimum_graph_size, screen_size) - - # Add bars widget - tabWidget.addTab(bars.barsWidget(), "Bar Graph") - # Add scatter widget - tabWidget.addTab(scatter.scatterWidget(), "Scatter Graph") - # Add surface widget - tabWidget.addTab(surface.surfaceWidget(), "Surface Graph") - - tabWidget.show() - sys.exit(app.exec()) diff --git a/examples/graphs/widgetgallery/rainfalldata.py b/examples/graphs/widgetgallery/rainfalldata.py deleted file mode 100644 index d74f45a8..00000000 --- a/examples/graphs/widgetgallery/rainfalldata.py +++ /dev/null @@ -1,125 +0,0 @@ -# Copyright (C) 2023 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -import sys - -from pathlib import Path - -from PySide6.QtCore import QFile, QIODevice, QObject -from PySide6.QtGraphs import (QBar3DSeries, QCategory3DAxis, QValue3DAxis) - -from variantbardataproxy import VariantBarDataProxy -from variantbardatamapping import VariantBarDataMapping -from variantdataset import VariantDataSet - - -MONTHS = ["January", "February", "March", "April", - "May", "June", "July", "August", "September", "October", - "November", "December"] - - -class RainfallData(QObject): - - def __init__(self): - super().__init__() - self._columnCount = 0 - self._rowCount = 0 - self._years = [] - self._numericMonths = [] - self._proxy = VariantBarDataProxy() - self._mapping = None - self._dataSet = None - self._series = QBar3DSeries() - self._valueAxis = QValue3DAxis() - self._rowAxis = QCategory3DAxis() - self._colAxis = QCategory3DAxis() - - # In data file the months are in numeric format, so create custom list - for i in range(1, 13): - self._numericMonths.append(str(i)) - - self._columnCount = len(self._numericMonths) - - self.updateYearsList(2010, 2022) - - # Create proxy and series - self._proxy = VariantBarDataProxy() - self._series = QBar3DSeries(self._proxy) - - self._series.setItemLabelFormat("%.1f mm") - - # Create the axes - self._rowAxis = QCategory3DAxis(self) - self._colAxis = QCategory3DAxis(self) - self._valueAxis = QValue3DAxis(self) - self._rowAxis.setAutoAdjustRange(True) - self._colAxis.setAutoAdjustRange(True) - self._valueAxis.setAutoAdjustRange(True) - - # Set axis labels and titles - self._rowAxis.setTitle("Year") - self._colAxis.setTitle("Month") - self._valueAxis.setTitle("rainfall (mm)") - self._valueAxis.setSegmentCount(5) - self._rowAxis.setLabels(self._years) - self._colAxis.setLabels(MONTHS) - self._rowAxis.setTitleVisible(True) - self._colAxis.setTitleVisible(True) - self._valueAxis.setTitleVisible(True) - - self.addDataSet() - - def customSeries(self): - return self._series - - def valueAxis(self): - return self._valueAxis - - def rowAxis(self): - return self._rowAxis - - def colAxis(self): - return self._colAxis - - def updateYearsList(self, start, end): - self._years.clear() - for i in range(start, end + 1): - self._years.append(str(i)) - self._rowCount = len(self._years) - - def addDataSet(self): - # Create a new variant data set and data item list - self._dataSet = VariantDataSet() - itemList = [] - - # Read data from a data file into the data item list - file_path = Path(__file__).resolve().parent / "data" / "raindata.txt" - dataFile = QFile(file_path) - if dataFile.open(QIODevice.ReadOnly | QIODevice.Text): - data = dataFile.readAll().data().decode("utf8") - for line in data.split("\n"): - if line and not line.startswith("#"): # Ignore comments - tokens = line.split(",") - # Each line has three data items: Year, month, and - # rainfall value - if len(tokens) >= 3: - # Store year and month as strings, and rainfall value - # as double into a variant data item and add the item to - # the item list. - newItem = [] - newItem.append(tokens[0].strip()) - newItem.append(tokens[1].strip()) - newItem.append(float(tokens[2].strip())) - itemList.append(newItem) - else: - print("Unable to open data file:", dataFile.fileName(), - file=sys.stderr) - - # Add items to the data set and set it to the proxy - self._dataSet.addItems(itemList) - self._proxy.setDataSet(self._dataSet) - - # Create new mapping for the data and set it to the proxy - self._mapping = VariantBarDataMapping(0, 1, 2, - self._years, self._numericMonths) - self._proxy.setMapping(self._mapping) diff --git a/examples/graphs/widgetgallery/scatterdatamodifier.py b/examples/graphs/widgetgallery/scatterdatamodifier.py deleted file mode 100644 index a9ca3822..00000000 --- a/examples/graphs/widgetgallery/scatterdatamodifier.py +++ /dev/null @@ -1,150 +0,0 @@ -# Copyright (C) 2023 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -from math import cos, degrees, sqrt - -from PySide6.QtCore import QObject, Signal, Slot, Qt -from PySide6.QtGui import QVector3D -from PySide6.QtGraphs import (QAbstract3DGraph, QAbstract3DSeries, - QScatterDataItem, QScatterDataProxy, - QScatter3DSeries, Q3DCamera, Q3DTheme) - -from axesinputhandler import AxesInputHandler - - -NUMBER_OF_ITEMS = 10000 -CURVE_DIVIDER = 7.5 -LOWER_NUMBER_OF_ITEMS = 900 -LOWER_CURVE_DIVIDER = 0.75 - - -class ScatterDataModifier(QObject): - - backgroundEnabledChanged = Signal(bool) - gridEnabledChanged = Signal(bool) - shadowQualityChanged = Signal(int) - - def __init__(self, scatter, parent): - super().__init__(parent) - - self._graph = scatter - - self._style = QAbstract3DSeries.MeshSphere - self._smooth = True - self._inputHandler = AxesInputHandler(scatter) - self._autoAdjust = True - self._itemCount = LOWER_NUMBER_OF_ITEMS - self._CURVE_DIVIDER = LOWER_CURVE_DIVIDER - self._inputHandler = AxesInputHandler(scatter) - - self._graph.activeTheme().setType(Q3DTheme.ThemeStoneMoss) - self._graph.setShadowQuality(QAbstract3DGraph.ShadowQualitySoftHigh) - self._graph.scene().activeCamera().setCameraPreset(Q3DCamera.CameraPresetFront) - self._graph.scene().activeCamera().setZoomLevel(80.0) - - self._proxy = QScatterDataProxy() - self._series = QScatter3DSeries(self._proxy) - self._series.setItemLabelFormat("@xTitle: @xLabel @yTitle: @yLabel @zTitle: @zLabel") - self._series.setMeshSmooth(self._smooth) - self._graph.addSeries(self._series) - - # Give ownership of the handler to the graph and make it the active - # handler - self._graph.setActiveInputHandler(self._inputHandler) - - # Give our axes to the input handler - self._inputHandler.setAxes(self._graph.axisX(), self._graph.axisZ(), - self._graph.axisY()) - - self.addData() - - def addData(self): - # Configure the axes according to the data - self._graph.axisX().setTitle("X") - self._graph.axisY().setTitle("Y") - self._graph.axisZ().setTitle("Z") - - dataArray = [] - limit = int(sqrt(self._itemCount) / 2.0) - for i in range(-limit, limit): - for j in range(-limit, limit): - x = float(i) + 0.5 - y = cos(degrees(float(i * j) / self._CURVE_DIVIDER)) - z = float(j) + 0.5 - dataArray.append(QScatterDataItem(QVector3D(x, y, z))) - - self._graph.seriesList()[0].dataProxy().resetArray(dataArray) - - @Slot(int) - def changeStyle(self, style): - comboBox = self.sender() - if comboBox: - self._style = comboBox.itemData(style) - if self._graph.seriesList(): - self._graph.seriesList()[0].setMesh(self._style) - - @Slot(int) - def setSmoothDots(self, smooth): - self._smooth = smooth == Qt.Checked.value - series = self._graph.seriesList()[0] - series.setMeshSmooth(self._smooth) - - @Slot(int) - def changeTheme(self, theme): - currentTheme = self._graph.activeTheme() - currentTheme.setType(Q3DTheme.Theme(theme)) - self.backgroundEnabledChanged.emit(currentTheme.isBackgroundEnabled()) - self.gridEnabledChanged.emit(currentTheme.isGridEnabled()) - - @Slot() - def changePresetCamera(self): - preset = Q3DCamera.CameraPresetFrontLow.value - - camera = self._graph.scene().activeCamera() - camera.setCameraPreset(Q3DCamera.CameraPreset(preset)) - - preset += 1 - if preset > Q3DCamera.CameraPresetDirectlyBelow.value: - preset = Q3DCamera.CameraPresetFrontLow.value - - @Slot(QAbstract3DGraph.ShadowQuality) - def shadowQualityUpdatedByVisual(self, sq): - self.shadowQualityChanged.emit(sq.value) - - @Slot(int) - def changeShadowQuality(self, quality): - sq = QAbstract3DGraph.ShadowQuality(quality) - self._graph.setShadowQuality(sq) - - @Slot(int) - def setBackgroundEnabled(self, enabled): - self._graph.activeTheme().setBackgroundEnabled(enabled == Qt.Checked.value) - - @Slot(int) - def setGridEnabled(self, enabled): - self._graph.activeTheme().setGridEnabled(enabled == Qt.Checked.value) - - @Slot() - def toggleItemCount(self): - if self._itemCount == NUMBER_OF_ITEMS: - self._itemCount = LOWER_NUMBER_OF_ITEMS - self._CURVE_DIVIDER = LOWER_CURVE_DIVIDER - else: - self._itemCount = NUMBER_OF_ITEMS - self._CURVE_DIVIDER = CURVE_DIVIDER - - self._graph.seriesList()[0].dataProxy().resetArray([]) - self.addData() - - @Slot() - def toggleRanges(self): - if not self._autoAdjust: - self._graph.axisX().setAutoAdjustRange(True) - self._graph.axisZ().setAutoAdjustRange(True) - self._inputHandler.setDragSpeedModifier(1.5) - self._autoAdjust = True - else: - self._graph.axisX().setRange(-10.0, 10.0) - self._graph.axisZ().setRange(-10.0, 10.0) - self._inputHandler.setDragSpeedModifier(15.0) - self._autoAdjust = False diff --git a/examples/graphs/widgetgallery/scattergraph.py b/examples/graphs/widgetgallery/scattergraph.py deleted file mode 100644 index 236348e5..00000000 --- a/examples/graphs/widgetgallery/scattergraph.py +++ /dev/null @@ -1,121 +0,0 @@ -# Copyright (C) 2023 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -from PySide6.QtCore import QObject, QSize, Qt -from PySide6.QtWidgets import (QCheckBox, QComboBox, QCommandLinkButton, - QLabel, QHBoxLayout, QSizePolicy, - QVBoxLayout, QWidget, ) -from PySide6.QtQuickWidgets import QQuickWidget -from PySide6.QtGraphs import (QAbstract3DSeries, Q3DScatter) - -from scatterdatamodifier import ScatterDataModifier - - -class ScatterGraph(QObject): - - def __init__(self, minimum_graph_size, maximum_graph_size): - super().__init__() - self._scatterGraph = Q3DScatter() - self._scatterWidget = QWidget() - hLayout = QHBoxLayout(self._scatterWidget) - self._scatterGraph.setMinimumSize(minimum_graph_size) - self._scatterGraph.setMaximumSize(maximum_graph_size) - self._scatterGraph.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) - self._scatterGraph.setFocusPolicy(Qt.StrongFocus) - self._scatterGraph.setResizeMode(QQuickWidget.SizeRootObjectToView) - hLayout.addWidget(self._scatterGraph, 1) - - vLayout = QVBoxLayout() - hLayout.addLayout(vLayout) - - cameraButton = QCommandLinkButton(self._scatterWidget) - cameraButton.setText("Change camera preset") - cameraButton.setDescription("Switch between a number of preset camera positions") - cameraButton.setIconSize(QSize(0, 0)) - - itemCountButton = QCommandLinkButton(self._scatterWidget) - itemCountButton.setText("Toggle item count") - itemCountButton.setDescription("Switch between 900 and 10000 data points") - itemCountButton.setIconSize(QSize(0, 0)) - - rangeButton = QCommandLinkButton(self._scatterWidget) - rangeButton.setText("Toggle axis ranges") - rangeButton.setDescription("Switch between automatic axis ranges and preset ranges") - rangeButton.setIconSize(QSize(0, 0)) - - backgroundCheckBox = QCheckBox(self._scatterWidget) - backgroundCheckBox.setText("Show background") - backgroundCheckBox.setChecked(True) - - gridCheckBox = QCheckBox(self._scatterWidget) - gridCheckBox.setText("Show grid") - gridCheckBox.setChecked(True) - - smoothCheckBox = QCheckBox(self._scatterWidget) - smoothCheckBox.setText("Smooth dots") - smoothCheckBox.setChecked(True) - - itemStyleList = QComboBox(self._scatterWidget) - itemStyleList.addItem("Sphere", QAbstract3DSeries.MeshSphere) - itemStyleList.addItem("Cube", QAbstract3DSeries.MeshCube) - itemStyleList.addItem("Minimal", QAbstract3DSeries.MeshMinimal) - itemStyleList.addItem("Point", QAbstract3DSeries.MeshPoint) - itemStyleList.setCurrentIndex(0) - - themeList = QComboBox(self._scatterWidget) - themeList.addItem("Qt") - themeList.addItem("Primary Colors") - themeList.addItem("Digia") - themeList.addItem("Stone Moss") - themeList.addItem("Army Blue") - themeList.addItem("Retro") - themeList.addItem("Ebony") - themeList.addItem("Isabelle") - themeList.setCurrentIndex(3) - - shadowQuality = QComboBox(self._scatterWidget) - shadowQuality.addItem("None") - shadowQuality.addItem("Low") - shadowQuality.addItem("Medium") - shadowQuality.addItem("High") - shadowQuality.addItem("Low Soft") - shadowQuality.addItem("Medium Soft") - shadowQuality.addItem("High Soft") - shadowQuality.setCurrentIndex(6) - - vLayout.addWidget(cameraButton) - vLayout.addWidget(itemCountButton) - vLayout.addWidget(rangeButton) - vLayout.addWidget(backgroundCheckBox) - vLayout.addWidget(gridCheckBox) - vLayout.addWidget(smoothCheckBox) - vLayout.addWidget(QLabel("Change dot style")) - vLayout.addWidget(itemStyleList) - vLayout.addWidget(QLabel("Change theme")) - vLayout.addWidget(themeList) - vLayout.addWidget(QLabel("Adjust shadow quality")) - vLayout.addWidget(shadowQuality, 1, Qt.AlignTop) - - self._modifier = ScatterDataModifier(self._scatterGraph, self) - - cameraButton.clicked.connect(self._modifier.changePresetCamera) - itemCountButton.clicked.connect(self._modifier.toggleItemCount) - rangeButton.clicked.connect(self._modifier.toggleRanges) - - backgroundCheckBox.stateChanged.connect(self._modifier.setBackgroundEnabled) - gridCheckBox.stateChanged.connect(self._modifier.setGridEnabled) - smoothCheckBox.stateChanged.connect(self._modifier.setSmoothDots) - - self._modifier.backgroundEnabledChanged.connect(backgroundCheckBox.setChecked) - self._modifier.gridEnabledChanged.connect(gridCheckBox.setChecked) - itemStyleList.currentIndexChanged.connect(self._modifier.changeStyle) - - themeList.currentIndexChanged.connect(self._modifier.changeTheme) - - shadowQuality.currentIndexChanged.connect(self._modifier.changeShadowQuality) - - self._modifier.shadowQualityChanged.connect(shadowQuality.setCurrentIndex) - self._scatterGraph.shadowQualityChanged.connect(self._modifier.shadowQualityUpdatedByVisual) - - def scatterWidget(self): - return self._scatterWidget diff --git a/examples/graphs/widgetgallery/surfacegraph.py b/examples/graphs/widgetgallery/surfacegraph.py deleted file mode 100644 index 4052da82..00000000 --- a/examples/graphs/widgetgallery/surfacegraph.py +++ /dev/null @@ -1,256 +0,0 @@ -# Copyright (C) 2023 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -from surfacegraphmodifier import SurfaceGraphModifier - -from PySide6.QtCore import QObject, Qt -from PySide6.QtGui import QBrush, QIcon, QLinearGradient, QPainter, QPixmap -from PySide6.QtWidgets import (QGroupBox, QCheckBox, QLabel, QHBoxLayout, - QPushButton, QRadioButton, QSizePolicy, QSlider, - QVBoxLayout, QWidget) -from PySide6.QtQuickWidgets import QQuickWidget -from PySide6.QtGraphs import Q3DSurface - - -def gradientBtoYPB_Pixmap(): - grBtoY = QLinearGradient(0, 0, 1, 100) - grBtoY.setColorAt(1.0, Qt.black) - grBtoY.setColorAt(0.67, Qt.blue) - grBtoY.setColorAt(0.33, Qt.red) - grBtoY.setColorAt(0.0, Qt.yellow) - pm = QPixmap(24, 100) - with QPainter(pm) as pmp: - pmp.setBrush(QBrush(grBtoY)) - pmp.setPen(Qt.NoPen) - pmp.drawRect(0, 0, 24, 100) - return pm - - -def gradientGtoRPB_Pixmap(): - grGtoR = QLinearGradient(0, 0, 1, 100) - grGtoR.setColorAt(1.0, Qt.darkGreen) - grGtoR.setColorAt(0.5, Qt.yellow) - grGtoR.setColorAt(0.2, Qt.red) - grGtoR.setColorAt(0.0, Qt.darkRed) - pm = QPixmap(24, 100) - with QPainter(pm) as pmp: - pmp.setBrush(QBrush(grGtoR)) - pmp.setPen(Qt.NoPen) - pmp.drawRect(0, 0, 24, 100) - return pm - - -def highlightPixmap(): - HEIGHT = 400 - WIDTH = 110 - BORDER = 10 - gr = QLinearGradient(0, 0, 1, HEIGHT - 2 * BORDER) - gr.setColorAt(1.0, Qt.black) - gr.setColorAt(0.8, Qt.darkGreen) - gr.setColorAt(0.6, Qt.green) - gr.setColorAt(0.4, Qt.yellow) - gr.setColorAt(0.2, Qt.red) - gr.setColorAt(0.0, Qt.darkRed) - pmHighlight = QPixmap(WIDTH, HEIGHT) - pmHighlight.fill(Qt.transparent) - with QPainter(pmHighlight) as pmpHighlight: - pmpHighlight.setBrush(QBrush(gr)) - pmpHighlight.setPen(Qt.NoPen) - pmpHighlight.drawRect(BORDER, BORDER, 35, HEIGHT - 2 * BORDER) - pmpHighlight.setPen(Qt.black) - step = (HEIGHT - 2 * BORDER) / 5 - for i in range(0, 6): - yPos = i * step + BORDER - pmpHighlight.drawLine(BORDER, yPos, 55, yPos) - HEIGHT = 550 - (i * 110) - pmpHighlight.drawText(60, yPos + 2, f"{HEIGHT} m") - return pmHighlight - - -class SurfaceGraph(QObject): - - def __init__(self, minimum_graph_size, maximum_graph_size): - super().__init__() - self._surfaceGraph = Q3DSurface() - self._surfaceWidget = QWidget() - hLayout = QHBoxLayout(self._surfaceWidget) - self._surfaceGraph.setMinimumSize(minimum_graph_size) - self._surfaceGraph.setMaximumSize(maximum_graph_size) - self._surfaceGraph.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) - self._surfaceGraph.setFocusPolicy(Qt.StrongFocus) - self._surfaceGraph.setResizeMode(QQuickWidget.SizeRootObjectToView) - hLayout.addWidget(self._surfaceGraph, 1) - vLayout = QVBoxLayout() - hLayout.addLayout(vLayout) - vLayout.setAlignment(Qt.AlignTop) - # Create control widgets - modelGroupBox = QGroupBox("Model") - sqrtSinModelRB = QRadioButton(self._surfaceWidget) - sqrtSinModelRB.setText("Sqrt and Sin") - sqrtSinModelRB.setChecked(False) - heightMapModelRB = QRadioButton(self._surfaceWidget) - heightMapModelRB.setText("Multiseries\nHeight Map") - heightMapModelRB.setChecked(False) - texturedModelRB = QRadioButton(self._surfaceWidget) - texturedModelRB.setText("Textured\nTopography") - texturedModelRB.setChecked(False) - modelVBox = QVBoxLayout() - modelVBox.addWidget(sqrtSinModelRB) - modelVBox.addWidget(heightMapModelRB) - modelVBox.addWidget(texturedModelRB) - modelGroupBox.setLayout(modelVBox) - selectionGroupBox = QGroupBox("Graph Selection Mode") - modeNoneRB = QRadioButton(self._surfaceWidget) - modeNoneRB.setText("No selection") - modeNoneRB.setChecked(False) - modeItemRB = QRadioButton(self._surfaceWidget) - modeItemRB.setText("Item") - modeItemRB.setChecked(False) - modeSliceRowRB = QRadioButton(self._surfaceWidget) - modeSliceRowRB.setText("Row Slice") - modeSliceRowRB.setChecked(False) - modeSliceColumnRB = QRadioButton(self._surfaceWidget) - modeSliceColumnRB.setText("Column Slice") - modeSliceColumnRB.setChecked(False) - selectionVBox = QVBoxLayout() - selectionVBox.addWidget(modeNoneRB) - selectionVBox.addWidget(modeItemRB) - selectionVBox.addWidget(modeSliceRowRB) - selectionVBox.addWidget(modeSliceColumnRB) - selectionGroupBox.setLayout(selectionVBox) - axisGroupBox = QGroupBox("Axis ranges") - axisMinSliderX = QSlider(Qt.Horizontal) - axisMinSliderX.setMinimum(0) - axisMinSliderX.setTickInterval(1) - axisMinSliderX.setEnabled(True) - axisMaxSliderX = QSlider(Qt.Horizontal) - axisMaxSliderX.setMinimum(1) - axisMaxSliderX.setTickInterval(1) - axisMaxSliderX.setEnabled(True) - axisMinSliderZ = QSlider(Qt.Horizontal) - axisMinSliderZ.setMinimum(0) - axisMinSliderZ.setTickInterval(1) - axisMinSliderZ.setEnabled(True) - axisMaxSliderZ = QSlider(Qt.Horizontal) - axisMaxSliderZ.setMinimum(1) - axisMaxSliderZ.setTickInterval(1) - axisMaxSliderZ.setEnabled(True) - axisVBox = QVBoxLayout(axisGroupBox) - axisVBox.addWidget(QLabel("Column range")) - axisVBox.addWidget(axisMinSliderX) - axisVBox.addWidget(axisMaxSliderX) - axisVBox.addWidget(QLabel("Row range")) - axisVBox.addWidget(axisMinSliderZ) - axisVBox.addWidget(axisMaxSliderZ) - # Mode-dependent controls - # sqrt-sin - colorGroupBox = QGroupBox("Custom gradient") - - pixmap = gradientBtoYPB_Pixmap() - gradientBtoYPB = QPushButton(self._surfaceWidget) - gradientBtoYPB.setIcon(QIcon(pixmap)) - gradientBtoYPB.setIconSize(pixmap.size()) - - pixmap = gradientGtoRPB_Pixmap() - gradientGtoRPB = QPushButton(self._surfaceWidget) - gradientGtoRPB.setIcon(QIcon(pixmap)) - gradientGtoRPB.setIconSize(pixmap.size()) - - colorHBox = QHBoxLayout(colorGroupBox) - colorHBox.addWidget(gradientBtoYPB) - colorHBox.addWidget(gradientGtoRPB) - # Multiseries heightmap - showGroupBox = QGroupBox("Show Object") - showGroupBox.setVisible(False) - checkboxShowOilRigOne = QCheckBox("Oil Rig 1") - checkboxShowOilRigOne.setChecked(True) - checkboxShowOilRigTwo = QCheckBox("Oil Rig 2") - checkboxShowOilRigTwo.setChecked(True) - checkboxShowRefinery = QCheckBox("Refinery") - showVBox = QVBoxLayout() - showVBox.addWidget(checkboxShowOilRigOne) - showVBox.addWidget(checkboxShowOilRigTwo) - showVBox.addWidget(checkboxShowRefinery) - showGroupBox.setLayout(showVBox) - visualsGroupBox = QGroupBox("Visuals") - visualsGroupBox.setVisible(False) - checkboxVisualsSeeThrough = QCheckBox("See-Through") - checkboxHighlightOil = QCheckBox("Highlight Oil") - checkboxShowShadows = QCheckBox("Shadows") - checkboxShowShadows.setChecked(True) - visualVBox = QVBoxLayout(visualsGroupBox) - visualVBox.addWidget(checkboxVisualsSeeThrough) - visualVBox.addWidget(checkboxHighlightOil) - visualVBox.addWidget(checkboxShowShadows) - labelSelection = QLabel("Selection:") - labelSelection.setVisible(False) - labelSelectedItem = QLabel("Nothing") - labelSelectedItem.setVisible(False) - # Textured topography heightmap - enableTexture = QCheckBox("Surface texture") - enableTexture.setVisible(False) - - label = QLabel(self._surfaceWidget) - label.setPixmap(highlightPixmap()) - heightMapGroupBox = QGroupBox("Highlight color map") - colorMapVBox = QVBoxLayout() - colorMapVBox.addWidget(label) - heightMapGroupBox.setLayout(colorMapVBox) - heightMapGroupBox.setVisible(False) - # Populate vertical layout - # Common - vLayout.addWidget(modelGroupBox) - vLayout.addWidget(selectionGroupBox) - vLayout.addWidget(axisGroupBox) - # Sqrt Sin - vLayout.addWidget(colorGroupBox) - # Multiseries heightmap - vLayout.addWidget(showGroupBox) - vLayout.addWidget(visualsGroupBox) - vLayout.addWidget(labelSelection) - vLayout.addWidget(labelSelectedItem) - # Textured topography - vLayout.addWidget(heightMapGroupBox) - vLayout.addWidget(enableTexture) - # Create the controller - modifier = SurfaceGraphModifier(self._surfaceGraph, labelSelectedItem, self) - # Connect widget controls to controller - heightMapModelRB.toggled.connect(modifier.enableHeightMapModel) - sqrtSinModelRB.toggled.connect(modifier.enableSqrtSinModel) - texturedModelRB.toggled.connect(modifier.enableTopographyModel) - modeNoneRB.toggled.connect(modifier.toggleModeNone) - modeItemRB.toggled.connect(modifier.toggleModeItem) - modeSliceRowRB.toggled.connect(modifier.toggleModeSliceRow) - modeSliceColumnRB.toggled.connect(modifier.toggleModeSliceColumn) - axisMinSliderX.valueChanged.connect(modifier.adjustXMin) - axisMaxSliderX.valueChanged.connect(modifier.adjustXMax) - axisMinSliderZ.valueChanged.connect(modifier.adjustZMin) - axisMaxSliderZ.valueChanged.connect(modifier.adjustZMax) - # Mode dependent connections - gradientBtoYPB.pressed.connect(modifier.setBlackToYellowGradient) - gradientGtoRPB.pressed.connect(modifier.setGreenToRedGradient) - checkboxShowOilRigOne.stateChanged.connect(modifier.toggleItemOne) - checkboxShowOilRigTwo.stateChanged.connect(modifier.toggleItemTwo) - checkboxShowRefinery.stateChanged.connect(modifier.toggleItemThree) - checkboxVisualsSeeThrough.stateChanged.connect(modifier.toggleSeeThrough) - checkboxHighlightOil.stateChanged.connect(modifier.toggleOilHighlight) - checkboxShowShadows.stateChanged.connect(modifier.toggleShadows) - enableTexture.stateChanged.connect(modifier.toggleSurfaceTexture) - # Connections to disable features depending on mode - sqrtSinModelRB.toggled.connect(colorGroupBox.setVisible) - heightMapModelRB.toggled.connect(showGroupBox.setVisible) - heightMapModelRB.toggled.connect(visualsGroupBox.setVisible) - heightMapModelRB.toggled.connect(labelSelection.setVisible) - heightMapModelRB.toggled.connect(labelSelectedItem.setVisible) - texturedModelRB.toggled.connect(enableTexture.setVisible) - texturedModelRB.toggled.connect(heightMapGroupBox.setVisible) - modifier.setAxisMinSliderX(axisMinSliderX) - modifier.setAxisMaxSliderX(axisMaxSliderX) - modifier.setAxisMinSliderZ(axisMinSliderZ) - modifier.setAxisMaxSliderZ(axisMaxSliderZ) - sqrtSinModelRB.setChecked(True) - modeItemRB.setChecked(True) - enableTexture.setChecked(True) - - def surfaceWidget(self): - return self._surfaceWidget diff --git a/examples/graphs/widgetgallery/surfacegraphmodifier.py b/examples/graphs/widgetgallery/surfacegraphmodifier.py deleted file mode 100644 index cb0ec4a8..00000000 --- a/examples/graphs/widgetgallery/surfacegraphmodifier.py +++ /dev/null @@ -1,642 +0,0 @@ -# Copyright (C) 2023 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -import os -from math import sqrt, sin -from pathlib import Path - -from PySide6.QtCore import QObject, QPropertyAnimation, Qt, Slot -from PySide6.QtGui import (QColor, QFont, QImage, QLinearGradient, - QQuaternion, QVector3D) -from PySide6.QtGraphs import (QAbstract3DGraph, QCustom3DItem, - QCustom3DLabel, QHeightMapSurfaceDataProxy, - QValue3DAxis, QSurfaceDataItem, - QSurfaceDataProxy, QSurface3DSeries, - Q3DInputHandler, Q3DCamera, Q3DTheme) - - -from highlightseries import HighlightSeries -from topographicseries import TopographicSeries -from custominputhandler import CustomInputHandler - - -SAMPLE_COUNT_X = 150 -SAMPLE_COUNT_Z = 150 -HEIGHTMAP_GRID_STEP_X = 6 -HEIGHTMAP_GRID_STEP_Z = 6 -SAMPLE_MIN = -8.0 -SAMPLE_MAX = 8.0 - -AREA_WIDTH = 8000.0 -AREA_HEIGHT = 8000.0 -ASPECT_RATIO = 0.1389 -MIN_RANGE = AREA_WIDTH * 0.49 - - -class SurfaceGraphModifier(QObject): - - def __init__(self, surface, label, parent): - super().__init__(parent) - self._data_path = Path(__file__).resolve().parent / "data" - self._graph = surface - self._textField = label - self._sqrtSinProxy = None - self._sqrtSinSeries = None - self._heightMapProxyOne = None - self._heightMapProxyTwo = None - self._heightMapProxyThree = None - self._heightMapSeriesOne = None - self._heightMapSeriesTwo = None - self._heightMapSeriesThree = None - - self._axisMinSliderX = None - self._axisMaxSliderX = None - self._axisMinSliderZ = None - self._axisMaxSliderZ = None - self._rangeMinX = 0.0 - self._rangeMinZ = 0.0 - self._stepX = 0.0 - self._stepZ = 0.0 - self._heightMapWidth = 0 - self._heightMapHeight = 0 - - self._selectionAnimation = None - self._titleLabel = None - self._previouslyAnimatedItem = None - self._previousScaling = {} - - self._topography = None - self._highlight = None - self._highlightWidth = 0 - self._highlightHeight = 0 - - self._customInputHandler = None - self._defaultInputHandler = Q3DInputHandler() - - ac = self._graph.scene().activeCamera() - ac.setZoomLevel(85.0) - ac.setCameraPreset(Q3DCamera.CameraPresetIsometricRight) - self._graph.activeTheme().setType(Q3DTheme.ThemeRetro) - - self._x_axis = QValue3DAxis() - self._y_axis = QValue3DAxis() - self._z_axis = QValue3DAxis() - self._graph.setAxisX(self._x_axis) - self._graph.setAxisY(self._y_axis) - self._graph.setAxisZ(self._z_axis) - - # - # Sqrt Sin - # - self._sqrtSinProxy = QSurfaceDataProxy() - self._sqrtSinSeries = QSurface3DSeries(self._sqrtSinProxy) - self.fillSqrtSinProxy() - - # - # Multisurface heightmap - # - # Create the first surface layer - heightMapImageOne = QImage(self._data_path / "layer_1.png") - self._heightMapProxyOne = QHeightMapSurfaceDataProxy(heightMapImageOne) - self._heightMapSeriesOne = QSurface3DSeries(self._heightMapProxyOne) - self._heightMapSeriesOne.setItemLabelFormat("(@xLabel, @zLabel): @yLabel") - self._heightMapProxyOne.setValueRanges(34.0, 40.0, 18.0, 24.0) - - # Create the other 2 surface layers - heightMapImageTwo = QImage(self._data_path / "layer_2.png") - self._heightMapProxyTwo = QHeightMapSurfaceDataProxy(heightMapImageTwo) - self._heightMapSeriesTwo = QSurface3DSeries(self._heightMapProxyTwo) - self._heightMapSeriesTwo.setItemLabelFormat("(@xLabel, @zLabel): @yLabel") - self._heightMapProxyTwo.setValueRanges(34.0, 40.0, 18.0, 24.0) - - heightMapImageThree = QImage(self._data_path / "layer_3.png") - self._heightMapProxyThree = QHeightMapSurfaceDataProxy(heightMapImageThree) - self._heightMapSeriesThree = QSurface3DSeries(self._heightMapProxyThree) - self._heightMapSeriesThree.setItemLabelFormat("(@xLabel, @zLabel): @yLabel") - self._heightMapProxyThree.setValueRanges(34.0, 40.0, 18.0, 24.0) - - # The images are the same size, so it's enough to get the dimensions - # from one - self._heightMapWidth = heightMapImageOne.width() - self._heightMapHeight = heightMapImageOne.height() - - # Set the gradients for multi-surface layers - grOne = QLinearGradient() - grOne.setColorAt(0.0, Qt.black) - grOne.setColorAt(0.38, Qt.darkYellow) - grOne.setColorAt(0.39, Qt.darkGreen) - grOne.setColorAt(0.5, Qt.darkGray) - grOne.setColorAt(1.0, Qt.gray) - self._heightMapSeriesOne.setBaseGradient(grOne) - self._heightMapSeriesOne.setColorStyle(Q3DTheme.ColorStyleRangeGradient) - - grTwo = QLinearGradient() - grTwo.setColorAt(0.39, Qt.blue) - grTwo.setColorAt(0.4, Qt.white) - self._heightMapSeriesTwo.setBaseGradient(grTwo) - self._heightMapSeriesTwo.setColorStyle(Q3DTheme.ColorStyleRangeGradient) - - grThree = QLinearGradient() - grThree.setColorAt(0.0, Qt.white) - grThree.setColorAt(0.05, Qt.black) - self._heightMapSeriesThree.setBaseGradient(grThree) - self._heightMapSeriesThree.setColorStyle(Q3DTheme.ColorStyleRangeGradient) - - # Custom items and label - self._graph.selectedElementChanged.connect(self.handleElementSelected) - - self._selectionAnimation = QPropertyAnimation(self) - self._selectionAnimation.setPropertyName(b"scaling") - self._selectionAnimation.setDuration(500) - self._selectionAnimation.setLoopCount(-1) - - titleFont = QFont("Century Gothic", 30) - titleFont.setBold(True) - self._titleLabel = QCustom3DLabel("Oil Rigs on Imaginary Sea", titleFont, - QVector3D(0.0, 1.2, 0.0), - QVector3D(1.0, 1.0, 0.0), - QQuaternion()) - self._titleLabel.setPositionAbsolute(True) - self._titleLabel.setFacingCamera(True) - self._titleLabel.setBackgroundColor(QColor(0x66cdaa)) - self._graph.addCustomItem(self._titleLabel) - self._titleLabel.setVisible(False) - - # Make two of the custom object visible - self.toggleItemOne(True) - self.toggleItemTwo(True) - - # - # Topographic map - # - self._topography = TopographicSeries() - file_name = os.fspath(self._data_path / "topography.png") - self._topography.setTopographyFile(file_name, AREA_WIDTH, AREA_HEIGHT) - self._topography.setItemLabelFormat("@yLabel m") - - self._highlight = HighlightSeries() - self._highlight.setTopographicSeries(self._topography) - self._highlight.setMinHeight(MIN_RANGE * ASPECT_RATIO) - self._highlight.handleGradientChange(AREA_WIDTH * ASPECT_RATIO) - self._graph.axisY().maxChanged.connect(self._highlight.handleGradientChange) - - self._customInputHandler = CustomInputHandler(self._graph) - self._customInputHandler.setHighlightSeries(self._highlight) - self._customInputHandler.setAxes(self._x_axis, self._y_axis, self._z_axis) - self._customInputHandler.setLimits(0.0, AREA_WIDTH, MIN_RANGE) - self._customInputHandler.setAspectRatio(ASPECT_RATIO) - - def fillSqrtSinProxy(self): - stepX = (SAMPLE_MAX - SAMPLE_MIN) / float(SAMPLE_COUNT_X - 1) - stepZ = (SAMPLE_MAX - SAMPLE_MIN) / float(SAMPLE_COUNT_Z - 1) - - dataArray = [] - for i in range(0, SAMPLE_COUNT_Z): - newRow = [] - # Keep values within range bounds, since just adding step can - # cause minor drift due to the rounding errors. - z = min(SAMPLE_MAX, (i * stepZ + SAMPLE_MIN)) - for j in range(0, SAMPLE_COUNT_X): - x = min(SAMPLE_MAX, (j * stepX + SAMPLE_MIN)) - R = sqrt(z * z + x * x) + 0.01 - y = (sin(R) / R + 0.24) * 1.61 - item = QSurfaceDataItem(QVector3D(x, y, z)) - newRow.append(item) - dataArray.append(newRow) - self._sqrtSinProxy.resetArray(dataArray) - - @Slot(bool) - def enableSqrtSinModel(self, enable): - if enable: - self._sqrtSinSeries.setDrawMode(QSurface3DSeries.DrawSurfaceAndWireframe) - self._sqrtSinSeries.setFlatShadingEnabled(True) - - self._graph.axisX().setLabelFormat("%.2f") - self._graph.axisZ().setLabelFormat("%.2f") - self._graph.axisX().setRange(SAMPLE_MIN, SAMPLE_MAX) - self._graph.axisY().setRange(0.0, 2.0) - self._graph.axisZ().setRange(SAMPLE_MIN, SAMPLE_MAX) - self._graph.axisX().setLabelAutoRotation(30.0) - self._graph.axisY().setLabelAutoRotation(90.0) - self._graph.axisZ().setLabelAutoRotation(30.0) - - self._graph.removeSeries(self._heightMapSeriesOne) - self._graph.removeSeries(self._heightMapSeriesTwo) - self._graph.removeSeries(self._heightMapSeriesThree) - self._graph.removeSeries(self._topography) - self._graph.removeSeries(self._highlight) - - self._graph.addSeries(self._sqrtSinSeries) - - self._titleLabel.setVisible(False) - self._graph.axisX().setTitleVisible(False) - self._graph.axisY().setTitleVisible(False) - self._graph.axisZ().setTitleVisible(False) - - self._graph.axisX().setTitle("") - self._graph.axisY().setTitle("") - self._graph.axisZ().setTitle("") - - self._graph.setActiveInputHandler(self._defaultInputHandler) - - # Reset range sliders for Sqrt & Sin - self._rangeMinX = SAMPLE_MIN - self._rangeMinZ = SAMPLE_MIN - self._stepX = (SAMPLE_MAX - SAMPLE_MIN) / float(SAMPLE_COUNT_X - 1) - self._stepZ = (SAMPLE_MAX - SAMPLE_MIN) / float(SAMPLE_COUNT_Z - 1) - self._axisMinSliderX.setMinimum(0) - self._axisMinSliderX.setMaximum(SAMPLE_COUNT_X - 2) - self._axisMinSliderX.setValue(0) - self._axisMaxSliderX.setMinimum(1) - self._axisMaxSliderX.setMaximum(SAMPLE_COUNT_X - 1) - self._axisMaxSliderX.setValue(SAMPLE_COUNT_X - 1) - self._axisMinSliderZ.setMinimum(0) - self._axisMinSliderZ.setMaximum(SAMPLE_COUNT_Z - 2) - self._axisMinSliderZ.setValue(0) - self._axisMaxSliderZ.setMinimum(1) - self._axisMaxSliderZ.setMaximum(SAMPLE_COUNT_Z - 1) - self._axisMaxSliderZ.setValue(SAMPLE_COUNT_Z - 1) - - @Slot(bool) - def enableHeightMapModel(self, enable): - if enable: - self._heightMapSeriesOne.setDrawMode(QSurface3DSeries.DrawSurface) - self._heightMapSeriesOne.setFlatShadingEnabled(False) - self._heightMapSeriesTwo.setDrawMode(QSurface3DSeries.DrawSurface) - self._heightMapSeriesTwo.setFlatShadingEnabled(False) - self._heightMapSeriesThree.setDrawMode(QSurface3DSeries.DrawSurface) - self._heightMapSeriesThree.setFlatShadingEnabled(False) - - self._graph.axisX().setLabelFormat("%.1f N") - self._graph.axisZ().setLabelFormat("%.1f E") - self._graph.axisX().setRange(34.0, 40.0) - self._graph.axisY().setAutoAdjustRange(True) - self._graph.axisZ().setRange(18.0, 24.0) - - self._graph.axisX().setTitle("Latitude") - self._graph.axisY().setTitle("Height") - self._graph.axisZ().setTitle("Longitude") - - self._graph.removeSeries(self._sqrtSinSeries) - self._graph.removeSeries(self._topography) - self._graph.removeSeries(self._highlight) - self._graph.addSeries(self._heightMapSeriesOne) - self._graph.addSeries(self._heightMapSeriesTwo) - self._graph.addSeries(self._heightMapSeriesThree) - - self._graph.setActiveInputHandler(self._defaultInputHandler) - - self._titleLabel.setVisible(True) - self._graph.axisX().setTitleVisible(True) - self._graph.axisY().setTitleVisible(True) - self._graph.axisZ().setTitleVisible(True) - - # Reset range sliders for height map - mapGridCountX = self._heightMapWidth / HEIGHTMAP_GRID_STEP_X - mapGridCountZ = self._heightMapHeight / HEIGHTMAP_GRID_STEP_Z - self._rangeMinX = 34.0 - self._rangeMinZ = 18.0 - self._stepX = 6.0 / float(mapGridCountX - 1) - self._stepZ = 6.0 / float(mapGridCountZ - 1) - self._axisMinSliderX.setMinimum(0) - self._axisMinSliderX.setMaximum(mapGridCountX - 2) - self._axisMinSliderX.setValue(0) - self._axisMaxSliderX.setMinimum(1) - self._axisMaxSliderX.setMaximum(mapGridCountX - 1) - self._axisMaxSliderX.setValue(mapGridCountX - 1) - self._axisMinSliderZ.setMinimum(0) - self._axisMinSliderZ.setMaximum(mapGridCountZ - 2) - self._axisMinSliderZ.setValue(0) - self._axisMaxSliderZ.setMinimum(1) - self._axisMaxSliderZ.setMaximum(mapGridCountZ - 1) - self._axisMaxSliderZ.setValue(mapGridCountZ - 1) - - @Slot(bool) - def enableTopographyModel(self, enable): - if enable: - self._graph.axisX().setLabelFormat("%i") - self._graph.axisZ().setLabelFormat("%i") - self._graph.axisX().setRange(0.0, AREA_WIDTH) - self._graph.axisY().setRange(100.0, AREA_WIDTH * ASPECT_RATIO) - self._graph.axisZ().setRange(0.0, AREA_HEIGHT) - self._graph.axisX().setLabelAutoRotation(30.0) - self._graph.axisY().setLabelAutoRotation(90.0) - self._graph.axisZ().setLabelAutoRotation(30.0) - - self._graph.removeSeries(self._heightMapSeriesOne) - self._graph.removeSeries(self._heightMapSeriesTwo) - self._graph.removeSeries(self._heightMapSeriesThree) - self._graph.addSeries(self._topography) - self._graph.addSeries(self._highlight) - - self._titleLabel.setVisible(False) - self._graph.axisX().setTitleVisible(False) - self._graph.axisY().setTitleVisible(False) - self._graph.axisZ().setTitleVisible(False) - - self._graph.axisX().setTitle("") - self._graph.axisY().setTitle("") - self._graph.axisZ().setTitle("") - - self._graph.setActiveInputHandler(self._customInputHandler) - - # Reset range sliders for topography map - self._rangeMinX = 0.0 - self._rangeMinZ = 0.0 - self._stepX = 1.0 - self._stepZ = 1.0 - self._axisMinSliderX.setMinimum(0) - self._axisMinSliderX.setMaximum(AREA_WIDTH - 200) - self._axisMinSliderX.setValue(0) - self._axisMaxSliderX.setMinimum(200) - self._axisMaxSliderX.setMaximum(AREA_WIDTH) - self._axisMaxSliderX.setValue(AREA_WIDTH) - self._axisMinSliderZ.setMinimum(0) - self._axisMinSliderZ.setMaximum(AREA_HEIGHT - 200) - self._axisMinSliderZ.setValue(0) - self._axisMaxSliderZ.setMinimum(200) - self._axisMaxSliderZ.setMaximum(AREA_HEIGHT) - self._axisMaxSliderZ.setValue(AREA_HEIGHT) - - def adjustXMin(self, min): - minX = self._stepX * float(min) + self._rangeMinX - - max = self._axisMaxSliderX.value() - if min >= max: - max = min + 1 - self._axisMaxSliderX.setValue(max) - - maxX = self._stepX * max + self._rangeMinX - - self.setAxisXRange(minX, maxX) - - def adjustXMax(self, max): - maxX = self._stepX * float(max) + self._rangeMinX - - min = self._axisMinSliderX.value() - if max <= min: - min = max - 1 - self._axisMinSliderX.setValue(min) - - minX = self._stepX * min + self._rangeMinX - - self.setAxisXRange(minX, maxX) - - def adjustZMin(self, min): - minZ = self._stepZ * float(min) + self._rangeMinZ - - max = self._axisMaxSliderZ.value() - if min >= max: - max = min + 1 - self._axisMaxSliderZ.setValue(max) - - maxZ = self._stepZ * max + self._rangeMinZ - - self.setAxisZRange(minZ, maxZ) - - def adjustZMax(self, max): - maxX = self._stepZ * float(max) + self._rangeMinZ - - min = self._axisMinSliderZ.value() - if max <= min: - min = max - 1 - self._axisMinSliderZ.setValue(min) - - minX = self._stepZ * min + self._rangeMinZ - - self.setAxisZRange(minX, maxX) - - def setAxisXRange(self, min, max): - self._graph.axisX().setRange(min, max) - - def setAxisZRange(self, min, max): - self._graph.axisZ().setRange(min, max) - - def setBlackToYellowGradient(self): - gr = QLinearGradient() - gr.setColorAt(0.0, Qt.black) - gr.setColorAt(0.33, Qt.blue) - gr.setColorAt(0.67, Qt.red) - gr.setColorAt(1.0, Qt.yellow) - - self._sqrtSinSeries.setBaseGradient(gr) - self._sqrtSinSeries.setColorStyle(Q3DTheme.ColorStyleRangeGradient) - - def setGreenToRedGradient(self): - gr = QLinearGradient() - gr.setColorAt(0.0, Qt.darkGreen) - gr.setColorAt(0.5, Qt.yellow) - gr.setColorAt(0.8, Qt.red) - gr.setColorAt(1.0, Qt.darkRed) - - self._sqrtSinSeries.setBaseGradient(gr) - self._sqrtSinSeries.setColorStyle(Q3DTheme.ColorStyleRangeGradient) - - @Slot(bool) - def toggleItemOne(self, show): - positionOne = QVector3D(39.0, 77.0, 19.2) - positionOnePipe = QVector3D(39.0, 45.0, 19.2) - positionOneLabel = QVector3D(39.0, 107.0, 19.2) - if show: - color = QImage(2, 2, QImage.Format_RGB32) - color.fill(Qt.red) - file_name = os.fspath(self._data_path / "oilrig.mesh") - item = QCustom3DItem(file_name, positionOne, - QVector3D(0.025, 0.025, 0.025), - QQuaternion.fromAxisAndAngle(0.0, 1.0, 0.0, 45.0), - color) - self._graph.addCustomItem(item) - file_name = os.fspath(self._data_path / "pipe.mesh") - item = QCustom3DItem(file_name, positionOnePipe, - QVector3D(0.005, 0.5, 0.005), QQuaternion(), - color) - item.setShadowCasting(False) - self._graph.addCustomItem(item) - - label = QCustom3DLabel() - label.setText("Oil Rig One") - label.setPosition(positionOneLabel) - label.setScaling(QVector3D(1.0, 1.0, 1.0)) - self._graph.addCustomItem(label) - else: - self.resetSelection() - self._graph.removeCustomItemAt(positionOne) - self._graph.removeCustomItemAt(positionOnePipe) - self._graph.removeCustomItemAt(positionOneLabel) - - @Slot(bool) - def toggleItemTwo(self, show): - positionTwo = QVector3D(34.5, 77.0, 23.4) - positionTwoPipe = QVector3D(34.5, 45.0, 23.4) - positionTwoLabel = QVector3D(34.5, 107.0, 23.4) - if show: - color = QImage(2, 2, QImage.Format_RGB32) - color.fill(Qt.red) - item = QCustom3DItem() - file_name = os.fspath(self._data_path / "oilrig.mesh") - item.setMeshFile(file_name) - item.setPosition(positionTwo) - item.setScaling(QVector3D(0.025, 0.025, 0.025)) - item.setRotation(QQuaternion.fromAxisAndAngle(0.0, 1.0, 0.0, 25.0)) - item.setTextureImage(color) - self._graph.addCustomItem(item) - file_name = os.fspath(self._data_path / "pipe.mesh") - item = QCustom3DItem(file_name, positionTwoPipe, - QVector3D(0.005, 0.5, 0.005), QQuaternion(), - color) - item.setShadowCasting(False) - self._graph.addCustomItem(item) - - label = QCustom3DLabel() - label.setText("Oil Rig Two") - label.setPosition(positionTwoLabel) - label.setScaling(QVector3D(1.0, 1.0, 1.0)) - self._graph.addCustomItem(label) - else: - self.resetSelection() - self._graph.removeCustomItemAt(positionTwo) - self._graph.removeCustomItemAt(positionTwoPipe) - self._graph.removeCustomItemAt(positionTwoLabel) - - @Slot(bool) - def toggleItemThree(self, show): - positionThree = QVector3D(34.5, 86.0, 19.1) - positionThreeLabel = QVector3D(34.5, 116.0, 19.1) - if show: - color = QImage(2, 2, QImage.Format_RGB32) - color.fill(Qt.darkMagenta) - item = QCustom3DItem() - file_name = os.fspath(self._data_path / "refinery.mesh") - item.setMeshFile(file_name) - item.setPosition(positionThree) - item.setScaling(QVector3D(0.04, 0.04, 0.04)) - item.setRotation(QQuaternion.fromAxisAndAngle(0.0, 1.0, 0.0, 75.0)) - item.setTextureImage(color) - self._graph.addCustomItem(item) - - label = QCustom3DLabel() - label.setText("Refinery") - label.setPosition(positionThreeLabel) - label.setScaling(QVector3D(1.0, 1.0, 1.0)) - self._graph.addCustomItem(label) - else: - self.resetSelection() - self._graph.removeCustomItemAt(positionThree) - self._graph.removeCustomItemAt(positionThreeLabel) - - @Slot(bool) - def toggleSeeThrough(self, seethrough): - s0 = self._graph.seriesList()[0] - s1 = self._graph.seriesList()[1] - if seethrough: - s0.setDrawMode(QSurface3DSeries.DrawWireframe) - s1.setDrawMode(QSurface3DSeries.DrawWireframe) - else: - s0.setDrawMode(QSurface3DSeries.DrawSurface) - s1.setDrawMode(QSurface3DSeries.DrawSurface) - - @Slot(bool) - def toggleOilHighlight(self, highlight): - s2 = self._graph.seriesList()[2] - if highlight: - grThree = QLinearGradient() - grThree.setColorAt(0.0, Qt.black) - grThree.setColorAt(0.05, Qt.red) - s2.setBaseGradient(grThree) - else: - grThree = QLinearGradient() - grThree.setColorAt(0.0, Qt.white) - grThree.setColorAt(0.05, Qt.black) - s2.setBaseGradient(grThree) - - @Slot(bool) - def toggleShadows(self, shadows): - sq = (QAbstract3DGraph.ShadowQualityMedium - if shadows else QAbstract3DGraph.ShadowQualityNone) - self._graph.setShadowQuality(sq) - - @Slot(bool) - def toggleSurfaceTexture(self, enable): - if enable: - file_name = os.fspath(self._data_path / "maptexture.jpg") - self._topography.setTextureFile(file_name) - else: - self._topography.setTextureFile("") - - def handleElementSelected(self, type): - self.resetSelection() - if type == QAbstract3DGraph.ElementCustomItem: - item = self._graph.selectedCustomItem() - text = "" - if isinstance(item, QCustom3DItem): - text += "Custom label: " - else: - file = item.meshFile().split("/")[-1] - text += f"{file}: " - - text += str(self._graph.selectedCustomItemIndex()) - self._textField.setText(text) - self._previouslyAnimatedItem = item - self._previousScaling = item.scaling() - self._selectionAnimation.setTargetObject(item) - self._selectionAnimation.setStartValue(item.scaling()) - self._selectionAnimation.setEndValue(item.scaling() * 1.5) - self._selectionAnimation.start() - elif type == QAbstract3DGraph.ElementSeries: - text = "Surface (" - series = self._graph.selectedSeries() - if series: - point = series.selectedPoint() - text += f"{point.x()}, {point.y()}" - text += ")" - self._textField.setText(text) - elif (type.value > QAbstract3DGraph.ElementSeries.value - and type < QAbstract3DGraph.ElementCustomItem.value): - index = self._graph.selectedLabelIndex() - text = "" - if type == QAbstract3DGraph.ElementAxisXLabel: - text += "Axis X label: " - elif type == QAbstract3DGraph.ElementAxisYLabel: - text += "Axis Y label: " - else: - text += "Axis Z label: " - text += str(index) - self._textField.setText(text) - else: - self._textField.setText("Nothing") - - def resetSelection(self): - self._selectionAnimation.stop() - if self._previouslyAnimatedItem: - self._previouslyAnimatedItem.setScaling(self._previousScaling) - self._previouslyAnimatedItem = None - - def toggleModeNone(self): - self._graph.setSelectionMode(QAbstract3DGraph.SelectionNone) - - def toggleModeItem(self): - self._graph.setSelectionMode(QAbstract3DGraph.SelectionItem) - - def toggleModeSliceRow(self): - sm = (QAbstract3DGraph.SelectionItemAndRow - | QAbstract3DGraph.SelectionSlice - | QAbstract3DGraph.SelectionMultiSeries) - self._graph.setSelectionMode(sm) - - def toggleModeSliceColumn(self): - sm = (QAbstract3DGraph.SelectionItemAndColumn - | QAbstract3DGraph.SelectionSlice - | QAbstract3DGraph.SelectionMultiSeries) - self._graph.setSelectionMode(sm) - - def setAxisMinSliderX(self, slider): - self._axisMinSliderX = slider - - def setAxisMaxSliderX(self, slider): - self._axisMaxSliderX = slider - - def setAxisMinSliderZ(self, slider): - self._axisMinSliderZ = slider - - def setAxisMaxSliderZ(self, slider): - self._axisMaxSliderZ = slider diff --git a/examples/graphs/widgetgallery/topographicseries.py b/examples/graphs/widgetgallery/topographicseries.py deleted file mode 100644 index 4f286a22..00000000 --- a/examples/graphs/widgetgallery/topographicseries.py +++ /dev/null @@ -1,57 +0,0 @@ -# Copyright (C) 2023 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -from PySide6.QtCore import Qt -from PySide6.QtGui import QImage, QVector3D -from PySide6.QtGraphs import (QSurface3DSeries, QSurfaceDataItem) - - -# Value used to encode height data as RGB value on PNG file -PACKING_FACTOR = 11983.0 - - -class TopographicSeries(QSurface3DSeries): - - def __init__(self): - super().__init__() - self._sampleCountX = 0.0 - self._sampleCountZ = 0.0 - self.setDrawMode(QSurface3DSeries.DrawSurface) - self.setFlatShadingEnabled(True) - self.setBaseColor(Qt.white) - - def sampleCountX(self): - return self._sampleCountX - - def sampleCountZ(self): - return self._sampleCountZ - - def setTopographyFile(self, file, width, height): - heightMapImage = QImage(file) - bits = heightMapImage.bits() - imageHeight = heightMapImage.height() - imageWidth = heightMapImage.width() - widthBits = imageWidth * 4 - stepX = width / float(imageWidth) - stepZ = height / float(imageHeight) - - dataArray = [] - for i in range(0, imageHeight): - p = i * widthBits - z = height - float(i) * stepZ - newRow = [] - for j in range(0, imageWidth): - aa = bits[p + 0] - rr = bits[p + 1] - gg = bits[p + 2] - color = (gg << 16) + (rr << 8) + aa - y = float(color) / PACKING_FACTOR - item = QSurfaceDataItem(QVector3D(float(j) * stepX, y, z)) - newRow.append(item) - p += 4 - dataArray.append(newRow) - - self.dataProxy().resetArray(dataArray) - - self._sampleCountX = float(imageWidth) - self._sampleCountZ = float(imageHeight) diff --git a/examples/graphs/widgetgallery/variantbardatamapping.py b/examples/graphs/widgetgallery/variantbardatamapping.py deleted file mode 100644 index 50bdefa6..00000000 --- a/examples/graphs/widgetgallery/variantbardatamapping.py +++ /dev/null @@ -1,67 +0,0 @@ -# Copyright (C) 2023 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -from PySide6.QtCore import QObject, Signal - - -class VariantBarDataMapping(QObject): - - rowIndexChanged = Signal() - columnIndexChanged = Signal() - valueIndexChanged = Signal() - rowCategoriesChanged = Signal() - columnCategoriesChanged = Signal() - mappingChanged = Signal() - - def __init__(self, rowIndex, columnIndex, valueIndex, - rowCategories=[], columnCategories=[]): - super().__init__(None) - self._rowIndex = rowIndex - self._columnIndex = columnIndex - self._valueIndex = valueIndex - self._rowCategories = rowCategories - self._columnCategories = columnCategories - - def setRowIndex(self, index): - self._rowIndex = index - self.mappingChanged.emit() - - def rowIndex(self): - return self._rowIndex - - def setColumnIndex(self, index): - self._columnIndex = index - self.mappingChanged.emit() - - def columnIndex(self): - return self._columnIndex - - def setValueIndex(self, index): - self._valueIndex = index - self.mappingChanged.emit() - - def valueIndex(self): - return self._valueIndex - - def setRowCategories(self, categories): - self._rowCategories = categories - self.mappingChanged.emit() - - def rowCategories(self): - return self._rowCategories - - def setColumnCategories(self, categories): - self._columnCategories = categories - self.mappingChanged.emit() - - def columnCategories(self): - return self._columnCategories - - def remap(self, rowIndex, columnIndex, valueIndex, - rowCategories=[], columnCategories=[]): - self._rowIndex = rowIndex - self._columnIndex = columnIndex - self._valueIndex = valueIndex - self._rowCategories = rowCategories - self._columnCategories = columnCategories - self.mappingChanged.emit() diff --git a/examples/graphs/widgetgallery/variantbardataproxy.py b/examples/graphs/widgetgallery/variantbardataproxy.py deleted file mode 100644 index 5ab2a2cd..00000000 --- a/examples/graphs/widgetgallery/variantbardataproxy.py +++ /dev/null @@ -1,100 +0,0 @@ -# Copyright (C) 2023 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -from PySide6.QtCore import Slot -from PySide6.QtGraphs import QBarDataProxy, QBarDataItem - - -class VariantBarDataProxy(QBarDataProxy): - - def __init__(self): - super().__init__() - self._dataSet = None - self._mapping = None - - def setDataSet(self, newSet): - if self._dataSet: - self._dataSet.itemsAdded.disconnect(self.handleItemsAdded) - self._dataSet.dataCleared.disconnect(self.handleDataCleared) - - self._dataSet = newSet - - if self._dataSet: - self._dataSet.itemsAdded.connect(self.handleItemsAdded) - self._dataSet.dataCleared.connect(self.handleDataCleared) - self.resolveDataSet() - - def dataSet(self): - return self._dataSet.data() - - # Map key (row, column, value) to value index in data item (VariantItem). - # Doesn't gain ownership of mapping, but does connect to it to listen for - # mapping changes. Modifying mapping that is set to proxy will trigger - # dataset re-resolving. - def setMapping(self, mapping): - if self._mapping: - self._mapping.mappingChanged.disconnect(self.handleMappingChanged) - - self._mapping = mapping - - if self._mapping: - self._mapping.mappingChanged.connect(self.handleMappingChanged) - - self.resolveDataSet() - - def mapping(self): - return self._mapping.data() - - @Slot(int, int) - def handleItemsAdded(self, index, count): - # Resolve new items - self.resolveDataSet() - - @Slot() - def handleDataCleared(self): - # Data cleared, reset array - self.resetArray(None) - - @Slot() - def handleMappingChanged(self): - self.resolveDataSet() - - # Resolve entire dataset into QBarDataArray. - def resolveDataSet(self): - # If we have no data or mapping, or the categories are not defined, - # simply clear the array - if (not self._dataSet or not self._mapping - or not self._mapping.rowCategories() - or not self._mapping.columnCategories()): - self.resetArray() - return - - itemList = self._dataSet.itemList() - - rowIndex = self._mapping.rowIndex() - columnIndex = self._mapping.columnIndex() - valueIndex = self._mapping.valueIndex() - rowList = self._mapping.rowCategories() - columnList = self._mapping.columnCategories() - - # Sort values into rows and columns - itemValueMap = {} - for item in itemList: - key = str(item[rowIndex]) - v = itemValueMap.get(key) - if not v: - v = {} - itemValueMap[key] = v - v[str(item[columnIndex])] = float(item[valueIndex]) - - # Create a new data array in format the parent class understands - newProxyArray = [] - for rowKey in rowList: - newProxyRow = [] - for i in range(0, len(columnList)): - item = QBarDataItem(itemValueMap[rowKey][columnList[i]]) - newProxyRow.append(item) - newProxyArray.append(newProxyRow) - - # Finally, reset the data array in the parent class - self.resetArray(newProxyArray) diff --git a/examples/graphs/widgetgallery/variantdataset.py b/examples/graphs/widgetgallery/variantdataset.py deleted file mode 100644 index 752bc388..00000000 --- a/examples/graphs/widgetgallery/variantdataset.py +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright (C) 2023 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -from PySide6.QtCore import QObject, Signal - - -class VariantDataSet(QObject): - - itemsAdded = Signal(int, int) - dataCleared = Signal() - - def __init__(self): - super().__init__() - self._variantData = [] - - def clear(self): - for item in self._variantData: - item.clear() - del item - - self._variantData.clear() - self.dataCleared.emit() - - def addItem(self, item): - self._variantData.append(item) - addIndex = len(self._variantData) - - self.itemsAdded.emit(addIndex, 1) - return addIndex - - def addItems(self, itemList): - newCount = len(itemList) - addIndex = len(self._variantData) - self._variantData.extend(itemList) - self.itemsAdded.emit(addIndex, newCount) - return addIndex - - def itemList(self): - return self._variantData diff --git a/examples/graphs/widgetgallery/widgetgallery.pyproject b/examples/graphs/widgetgallery/widgetgallery.pyproject deleted file mode 100644 index 581b2148..00000000 --- a/examples/graphs/widgetgallery/widgetgallery.pyproject +++ /dev/null @@ -1,29 +0,0 @@ -{ - "files": ["main.py", - "axesinputhandler.py", - "bargraph.py", - "custominputhandler.py", - "graphmodifier.py", - "highlightseries.py", - "rainfalldata.py", - "scatterdatamodifier.py", - "scattergraph.py", - "surfacegraph.py", - "surfacegraphmodifier.py", - "topographicseries.py", - "variantbardatamapping.py", - "variantbardataproxy.py", - "variantdataset.py", - "data/layer_1.png", - "data/layer_2.png", - "data/layer_3.png", - "data/license.txt", - "data/maptexture.jpg", - "data/narrowarrow.mesh", - "data/oilrig.mesh", - "data/pipe.mesh", - "data/raindata.txt", - "data/refinery.mesh", - "data/topography.png" -] -} diff --git a/examples/gui/rhiwindow/doc/rhiwindow.rst b/examples/gui/rhiwindow/doc/rhiwindow.rst index f38f9af6..defbc46e 100644 --- a/examples/gui/rhiwindow/doc/rhiwindow.rst +++ b/examples/gui/rhiwindow/doc/rhiwindow.rst @@ -1,3 +1,5 @@ +.. _rhi-window-example: + RHI Window Example ================== @@ -28,13 +30,7 @@ platform/API specifics or correctly handling ``QExposeEvent`` and resize events for the window are all managed by Qt Quick. Whereas in this example, all that is managed and taken care of by the application itself. -.. note:: For ``QWidget``-based applications in particular, it should be noted - that ``QWidget::createWindowContainer()`` allows embedding a ``QWindow`` - (backed by a native window) into the widget-based user interface. Therefore, - the ``HelloWindow`` class from this example is reusable in ``QWidget``-based - applications, assuming the necessary initialization from ``main()`` is in place - as well. - +.. note:: For ``QWidget``-based applications, see the :ref:`rhi-widget-example`. Shaders ------- diff --git a/examples/multimedia/audiooutput/audiooutput.py b/examples/multimedia/audiooutput/audiooutput.py index 5be9b0d6..06d52f68 100644 --- a/examples/multimedia/audiooutput/audiooutput.py +++ b/examples/multimedia/audiooutput/audiooutput.py @@ -177,11 +177,18 @@ class AudioTest(QMainWindow): self.m_audioSink.start(self.m_generator) self.m_volumeSlider.setValue(self.m_audioSink.volume() * 100) - @Slot(int) - def device_changed(self, index): + def closeEvent(self, e): + self.stop() + e.accept() + + def stop(self): self.m_pullTimer.stop() self.m_generator.stop() self.m_audioSink.stop() + + @Slot(int) + def device_changed(self, index): + self.stop() self.m_device = self.m_deviceBox.itemData(index) self.create_audio_output() diff --git a/examples/multimedia/audiosource/audiosource.py b/examples/multimedia/audiosource/audiosource.py index bc2d606c..a78beb58 100644 --- a/examples/multimedia/audiosource/audiosource.py +++ b/examples/multimedia/audiosource/audiosource.py @@ -25,7 +25,7 @@ from PySide6.QtWidgets import (QApplication, QComboBox, QPushButton, QSlider, QV is_android = os.environ.get('ANDROID_ARGUMENT') -if is_android: +if is_android or sys.platform == "darwin": from PySide6.QtCore import QMicrophonePermission @@ -93,7 +93,13 @@ class InputTest(QWidget): @Slot() def initialize(self): - if is_android: + if is_android or sys.platform == "darwin": + is_nuitka = "__compiled__" in globals() + if not is_nuitka and sys.platform == "darwin": + print("This example does not work on macOS when Python is run in interpreted mode." + "For this example to work on macOS, package the example using pyside6-deploy" + "For more information, read `Notes for Developer` in the documentation") + sys.exit(0) permission = QMicrophonePermission() permission_status = qApp.checkPermission(permission) # noqa: F821 if permission_status == Qt.PermissionStatus.Undetermined: diff --git a/examples/multimedia/camera/camera.py b/examples/multimedia/camera/camera.py index f66f28a2..fa379c80 100644 --- a/examples/multimedia/camera/camera.py +++ b/examples/multimedia/camera/camera.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause import os +import sys from pathlib import Path from PySide6.QtMultimedia import (QAudioInput, QCamera, QCameraDevice, @@ -16,8 +17,10 @@ from metadatadialog import MetaDataDialog from imagesettings import ImageSettings from videosettings import VideoSettings, is_android -if is_android: +if is_android or sys.platform == "darwin": from PySide6.QtCore import QMicrophonePermission, QCameraPermission + +if is_android: from ui_camera_mobile import Ui_Camera else: from ui_camera import Ui_Camera @@ -60,7 +63,14 @@ class Camera(QMainWindow): @Slot() def initialize(self): - if is_android: + if is_android or sys.platform == "darwin": + is_nuitka = "__compiled__" in globals() + if not is_nuitka and sys.platform == "darwin": + print("This example does not work on macOS when Python is run in interpreted mode." + "For this example to work on macOS, package the example using pyside6-deploy" + "For more information, read `Notes for Developer` in the documentation") + sys.exit(0) + # camera cam_permission = QCameraPermission() cam_permission_status = qApp.checkPermission(cam_permission) # noqa: F821 diff --git a/examples/multimedia/camera/ui_camera.py b/examples/multimedia/camera/ui_camera.py index 92e115c7..690cf335 100644 --- a/examples/multimedia/camera/ui_camera.py +++ b/examples/multimedia/camera/ui_camera.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'camera.ui' ## -## Created by: Qt User Interface Compiler version 6.5.0 +## Created by: Qt User Interface Compiler version 6.7.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -47,7 +47,7 @@ class Ui_Camera(object): self.tab_2.setObjectName(u"tab_2") self.gridLayout = QGridLayout(self.tab_2) self.gridLayout.setObjectName(u"gridLayout") - self.verticalSpacer_2 = QSpacerItem(20, 161, QSizePolicy.Minimum, QSizePolicy.Expanding) + self.verticalSpacer_2 = QSpacerItem(20, 161, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding) self.gridLayout.addItem(self.verticalSpacer_2, 3, 0, 1, 1) @@ -92,7 +92,7 @@ class Ui_Camera(object): self.gridLayout_2.addWidget(self.stopButton, 2, 0, 1, 1) - self.verticalSpacer = QSpacerItem(20, 76, QSizePolicy.Minimum, QSizePolicy.Expanding) + self.verticalSpacer = QSpacerItem(20, 76, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding) self.gridLayout_2.addItem(self.verticalSpacer, 3, 0, 1, 1) @@ -114,7 +114,7 @@ class Ui_Camera(object): self.stackedWidget = QStackedWidget(self.centralwidget) self.stackedWidget.setObjectName(u"stackedWidget") - sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) + sizePolicy = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred) sizePolicy.setHorizontalStretch(1) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.stackedWidget.sizePolicy().hasHeightForWidth()) @@ -147,7 +147,7 @@ class Ui_Camera(object): self.gridLayout_4.setObjectName(u"gridLayout_4") self.lastImagePreviewLabel = QLabel(self.previewPage) self.lastImagePreviewLabel.setObjectName(u"lastImagePreviewLabel") - sizePolicy1 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.MinimumExpanding) + sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.MinimumExpanding) sizePolicy1.setHorizontalStretch(0) sizePolicy1.setVerticalStretch(0) sizePolicy1.setHeightForWidth(self.lastImagePreviewLabel.sizePolicy().hasHeightForWidth()) diff --git a/examples/multimedia/camera/ui_camera_mobile.py b/examples/multimedia/camera/ui_camera_mobile.py index 9feab3d6..5cdd81f1 100644 --- a/examples/multimedia/camera/ui_camera_mobile.py +++ b/examples/multimedia/camera/ui_camera_mobile.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'camera_mobile.ui' ## -## Created by: Qt User Interface Compiler version 6.5.2 +## Created by: Qt User Interface Compiler version 6.7.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -42,7 +42,7 @@ class Ui_Camera(object): self.gridLayout_3.setObjectName(u"gridLayout_3") self.captureWidget = QTabWidget(self.centralwidget) self.captureWidget.setObjectName(u"captureWidget") - sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + sizePolicy = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.captureWidget.sizePolicy().hasHeightForWidth()) @@ -63,7 +63,7 @@ class Ui_Camera(object): self.label = QLabel(self.tab_2) self.label.setObjectName(u"label") - sizePolicy1 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed) + sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed) sizePolicy1.setHorizontalStretch(0) sizePolicy1.setVerticalStretch(0) sizePolicy1.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth()) @@ -109,7 +109,7 @@ class Ui_Camera(object): self.verticalLayout_2 = QVBoxLayout() self.verticalLayout_2.setObjectName(u"verticalLayout_2") - self.verticalSpacer = QSpacerItem(20, 10, QSizePolicy.Minimum, QSizePolicy.Expanding) + self.verticalSpacer = QSpacerItem(20, 10, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding) self.verticalLayout_2.addItem(self.verticalSpacer) @@ -137,7 +137,7 @@ class Ui_Camera(object): self.stackedWidget = QStackedWidget(self.centralwidget) self.stackedWidget.setObjectName(u"stackedWidget") - sizePolicy2 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + sizePolicy2 = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) sizePolicy2.setHorizontalStretch(1) sizePolicy2.setVerticalStretch(0) sizePolicy2.setHeightForWidth(self.stackedWidget.sizePolicy().hasHeightForWidth()) @@ -160,7 +160,7 @@ class Ui_Camera(object): self.gridLayout_5.setObjectName(u"gridLayout_5") self.viewfinder = QVideoWidget(self.viewfinderPage) self.viewfinder.setObjectName(u"viewfinder") - sizePolicy3 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + sizePolicy3 = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) sizePolicy3.setHorizontalStretch(0) sizePolicy3.setVerticalStretch(0) sizePolicy3.setHeightForWidth(self.viewfinder.sizePolicy().hasHeightForWidth()) @@ -175,7 +175,7 @@ class Ui_Camera(object): self.gridLayout_4.setObjectName(u"gridLayout_4") self.lastImagePreviewLabel = QLabel(self.previewPage) self.lastImagePreviewLabel.setObjectName(u"lastImagePreviewLabel") - sizePolicy4 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.MinimumExpanding) + sizePolicy4 = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.MinimumExpanding) sizePolicy4.setHorizontalStretch(0) sizePolicy4.setVerticalStretch(0) sizePolicy4.setHeightForWidth(self.lastImagePreviewLabel.sizePolicy().hasHeightForWidth()) diff --git a/examples/multimedia/camera/ui_imagesettings.py b/examples/multimedia/camera/ui_imagesettings.py index aa7505f8..a3fba778 100644 --- a/examples/multimedia/camera/ui_imagesettings.py +++ b/examples/multimedia/camera/ui_imagesettings.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'imagesettings.ui' ## -## Created by: Qt User Interface Compiler version 6.5.0 +## Created by: Qt User Interface Compiler version 6.7.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -65,7 +65,7 @@ class Ui_ImageSettingsUi(object): self.gridLayout.addWidget(self.groupBox_2, 0, 0, 1, 1) - self.verticalSpacer = QSpacerItem(20, 14, QSizePolicy.Minimum, QSizePolicy.Expanding) + self.verticalSpacer = QSpacerItem(20, 14, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding) self.gridLayout.addItem(self.verticalSpacer, 1, 0, 1, 1) diff --git a/examples/multimedia/camera/ui_videosettings.py b/examples/multimedia/camera/ui_videosettings.py index c84c93d7..eec626f2 100644 --- a/examples/multimedia/camera/ui_videosettings.py +++ b/examples/multimedia/camera/ui_videosettings.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'videosettings.ui' ## -## Created by: Qt User Interface Compiler version 6.5.0 +## Created by: Qt User Interface Compiler version 6.7.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -84,7 +84,7 @@ class Ui_VideoSettingsUi(object): self.widget = QWidget(VideoSettingsUi) self.widget.setObjectName(u"widget") - sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) + sizePolicy = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.widget.sizePolicy().hasHeightForWidth()) @@ -151,7 +151,7 @@ class Ui_VideoSettingsUi(object): self.gridLayout_3.addWidget(self.widget, 2, 0, 1, 1) - self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding) + self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding) self.gridLayout_3.addItem(self.verticalSpacer, 3, 0, 1, 1) diff --git a/examples/multimedia/camera/ui_videosettings_mobile.py b/examples/multimedia/camera/ui_videosettings_mobile.py index 85ec0820..50fb8e08 100644 --- a/examples/multimedia/camera/ui_videosettings_mobile.py +++ b/examples/multimedia/camera/ui_videosettings_mobile.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'videosettings_mobile.ui' ## -## Created by: Qt User Interface Compiler version 6.5.2 +## Created by: Qt User Interface Compiler version 6.7.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -29,7 +29,7 @@ class Ui_VideoSettingsUi(object): self.gridLayout_3.setObjectName(u"gridLayout_3") self.widget = QWidget(VideoSettingsUi) self.widget.setObjectName(u"widget") - sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) + sizePolicy = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.widget.sizePolicy().hasHeightForWidth()) diff --git a/examples/multimedia/player/player.py b/examples/multimedia/player/player.py index 4731386a..d28f2887 100644 --- a/examples/multimedia/player/player.py +++ b/examples/multimedia/player/player.py @@ -44,43 +44,43 @@ class MainWindow(QMainWindow): self.addToolBar(tool_bar) file_menu = self.menuBar().addMenu("&File") - icon = QIcon.fromTheme("document-open") + icon = QIcon.fromTheme(QIcon.ThemeIcon.DocumentOpen) open_action = QAction(icon, "&Open...", self, shortcut=QKeySequence.Open, triggered=self.open) file_menu.addAction(open_action) tool_bar.addAction(open_action) - icon = QIcon.fromTheme("application-exit") + icon = QIcon.fromTheme(QIcon.ThemeIcon.ApplicationExit) exit_action = QAction(icon, "E&xit", self, shortcut="Ctrl+Q", triggered=self.close) file_menu.addAction(exit_action) play_menu = self.menuBar().addMenu("&Play") style = self.style() - icon = QIcon.fromTheme("media-playback-start.png", + icon = QIcon.fromTheme(QIcon.ThemeIcon.MediaPlaybackStart, style.standardIcon(QStyle.SP_MediaPlay)) self._play_action = tool_bar.addAction(icon, "Play") self._play_action.triggered.connect(self._player.play) play_menu.addAction(self._play_action) - icon = QIcon.fromTheme("media-skip-backward-symbolic.svg", + icon = QIcon.fromTheme(QIcon.ThemeIcon.MediaSkipBackward, style.standardIcon(QStyle.SP_MediaSkipBackward)) self._previous_action = tool_bar.addAction(icon, "Previous") self._previous_action.triggered.connect(self.previous_clicked) play_menu.addAction(self._previous_action) - icon = QIcon.fromTheme("media-playback-pause.png", + icon = QIcon.fromTheme(QIcon.ThemeIcon.MediaPlaybackPause, style.standardIcon(QStyle.SP_MediaPause)) self._pause_action = tool_bar.addAction(icon, "Pause") self._pause_action.triggered.connect(self._player.pause) play_menu.addAction(self._pause_action) - icon = QIcon.fromTheme("media-skip-forward-symbolic.svg", + icon = QIcon.fromTheme(QIcon.ThemeIcon.MediaSkipForward, style.standardIcon(QStyle.SP_MediaSkipForward)) self._next_action = tool_bar.addAction(icon, "Next") self._next_action.triggered.connect(self.next_clicked) play_menu.addAction(self._next_action) - icon = QIcon.fromTheme("media-playback-stop.png", + icon = QIcon.fromTheme(QIcon.ThemeIcon.MediaPlaybackStop, style.standardIcon(QStyle.SP_MediaStop)) self._stop_action = tool_bar.addAction(icon, "Stop") self._stop_action.triggered.connect(self._ensure_stopped) @@ -99,8 +99,9 @@ class MainWindow(QMainWindow): self._volume_slider.valueChanged.connect(self._audio_output.setVolume) tool_bar.addWidget(self._volume_slider) + icon = QIcon.fromTheme(QIcon.ThemeIcon.HelpAbout) about_menu = self.menuBar().addMenu("&About") - about_qt_act = QAction("About &Qt", self, triggered=qApp.aboutQt) # noqa: F821 + about_qt_act = QAction(icon, "About &Qt", self, triggered=qApp.aboutQt) # noqa: F821 about_menu.addAction(about_qt_act) self._video_widget = QVideoWidget() diff --git a/examples/multimedia/screencapture/screencapturepreview.py b/examples/multimedia/screencapture/screencapturepreview.py index 3f75a060..c7e0c596 100644 --- a/examples/multimedia/screencapture/screencapturepreview.py +++ b/examples/multimedia/screencapture/screencapturepreview.py @@ -34,6 +34,7 @@ class ScreenCapturePreview(QWidget): self._screen_label = QLabel("Select screen to capture:", self) self._video_widget_label = QLabel("Capture output:", self) self._start_stop_button = QPushButton(self) + self._status_label = QLabel(self) self._screen_list_model = ScreenListModel(self) @@ -65,6 +66,7 @@ class ScreenCapturePreview(QWidget): grid_layout.addWidget(self._video_widget, 1, 1, 4, 1) grid_layout.addWidget(self._window_label, 2, 0) grid_layout.addWidget(self._window_list_view, 3, 0) + grid_layout.addWidget(self._status_label, 5, 0, 1, 2) grid_layout.setColumnStretch(1, 1) grid_layout.setRowStretch(1, 1) @@ -86,6 +88,7 @@ class ScreenCapturePreview(QWidget): @Slot(QItemSelection) def on_current_screen_selection_changed(self, selection): + self.clear_error_string() indexes = selection.indexes() if indexes: self._screen_capture.setScreen(self._screen_list_model.screen(indexes[0])) @@ -96,6 +99,7 @@ class ScreenCapturePreview(QWidget): @Slot(QItemSelection) def on_current_window_selection_changed(self, selection): + self.clear_error_string() indexes = selection.indexes() if indexes: window = self._window_list_model.window(indexes[0]) @@ -115,16 +119,23 @@ class ScreenCapturePreview(QWidget): @Slot(QWindowCapture.Error, str) def on_window_capture_error_occured(self, error, error_string): - QMessageBox.warning(self, "QWindowCapture: Error occurred", - error_string) + self.set_error_string("QWindowCapture: Error occurred " + error_string) @Slot(QScreenCapture.Error, str) def on_screen_capture_error_occured(self, error, error_string): - QMessageBox.warning(self, "QScreenCapture: Error occurred", - error_string) + self.set_error_string("QScreenCapture: Error occurred " + error_string) + + def set_error_string(self, t): + self._status_label.setStyleSheet("background-color: rgb(255, 0, 0);") + self._status_label.setText(t) + + def clear_error_string(self): + self._status_label.clear() + self._status_label.setStyleSheet("") @Slot() def on_start_stop_button_clicked(self): + self.clear_error_string() self.update_active(self._source_type, not self.is_active()) def update_start_stop_button_text(self): diff --git a/examples/pdfwidgets/pdfviewer/rc_resources.py b/examples/pdfwidgets/pdfviewer/rc_resources.py new file mode 100644 index 00000000..7e386e99 --- /dev/null +++ b/examples/pdfwidgets/pdfviewer/rc_resources.py @@ -0,0 +1,3468 @@ +# Resource object code (Python 3) +# Created by: object code +# Created by: The Resource Compiler for Qt version 6.4.0 +# WARNING! All changes made in this file will be lost! + +from PySide6 import QtCore + +qt_resource_data = b"\ +\x00\x001G\ +\x1f\ +\x8b\x08\x00\x00\x00\x00\x00\x00\x00\xec}\xebs\x1b\xc7\xb1\ +\xef\xe7\xe4\xaf\xe0U\xbeDU\xc0\xb0\xdf\x0f%\xce\xa9\ +\xc4\x8eS\xa9r\xea\xa4n\x1e\xf7\xdcO.\x88\x04%\ +\x1e\xd3\xa4\x0eI\xf9\x91\xbf\xfe\xee\x02$g!h\xb1\ +\x00\x16P$_s\x5c\xf2b\xb6\xb7{\xfa7==\ +=\xb33\xb3\xbf\xfd\x8f\x1f\xbe\xbd:\xf9n~{w\ +ys\xfd\xd93,\xf0\xecd~}vs~y\xfd\ +\xea\xb3g\xff\xf8\xfb\x97\xd3xvrw?\xbb>\x9f\ +]\xdd\x5c\xcf?{v}\xf3\xec?~\xf7\xcb\xdf\xfe\ +\xaf\xe9\xf4\xe4O\xf3\xeb\xf9\xed\xec\xfe\xe6\xf6\xc5\xc9\xef\ +\xcfo^\xceO\xfe|u\xf5\xf6\xee~\x91u\x82T\ +\xa0\xc0\xe4\xe4o\xff\xfc\xd3\xc9\x1f\x7fxss{\x7f\ +\xf2\xd7\xab\xb7\xaf\xa6\x7f\xbe>)\x8b\xcc\x7f.e\xbe\ +8\xb1\x02p\xf2\x87\xb7\x97W\xe7'\x8a\x22\xf1\xfc\xe4\ +d:mD\xdc}\xf7\xea\x97'''M\xf9\xae\xef\ +^\x5c\xdf}\xf6\xec\xf5\xfd\xfd\x9b\x17\xa7\xa7\xd7we\ +\xd6\x8a+g7\xdf\x9e\xfem\xf6\xdd\xfc\xcb\x9b\xdb\xff\ +3\x7fy\x8a\x05N\x9f\xd5'f\xef\x7f`Q\xd2F\ +\xfe?/\xe7\xdf\xcfo\xff\xf8\xc3\xfd\xfc\xba-\xc6\xdd\ +)\xaf>~\xb9\xe1\xf1\x8e\x9e\xa7\x08\xab\xcf\x9d\x9f=\ +=\xf8\xe6\xed\xedU\xb9\xb9}uz~v:\xbf\x9a\ +\x7f;\xbf\xbe\xbfkJ\x89]\xf2\xb3J~v;\x9f\ +\xdd_~7o\xc4|\xdb\x14\xa8}\xb2\x91\xfd\xab\x0e\ +\xf1\xed\xf9\xc5\x13\xf5\xf7\xdf\x7f_\xbe\xe7\x05\x11f\xe6\ +)\xd0)\xd1\xb4\xa1\x98\xde\xfdx}?\xfba\xba\xfa\ +h\x03\xe6\xfb\x1e%\x008m\xeeU\xca\xed\xa8^\xfc\ +puy\xfdMoa\x16w\xbb\xd2\x1b{zss\ +^1}\xcc(w7oo\xcf\xe6\x17\xcd\x93\xf3r\ +=\xbf?\xfd\xe2\xef_<\xdd\x9cB9\xbf?\xef\xb0\ +i\x98\xde\x9d\xcd\xde\xccW\xe4>f.\xf1\x9a};\ +\xbf{3;\x9b\xdf\x9d>\xe6\xb7\xcfw\x0d\x1c\x17\x19\ +\x97\xe7\x9f=\xfbj\xf6\xe3\xfc\xf6\xeb\xe5\xef\xef/\xcf\ +\xef_7\xb7)\x16?_\xcf/_\xbd\xbe\xaf\xbf\xbf\ +k\xac\xe5\x0f7?|\xf6\x0cN\xe0\x04)N\x1eo\ +\xdc4\x9c/\xaen\xbe\xff\xec\xd9w\x97w\x97/\xaf\ +\x96\xe2\xe6\xd7\xb3\xe6r\xfarv\xf6\xcd\xab\xdb\x9b\xb7\ +\xd7\x8d\xb0\xeb\xf9\xf7'\x9d\x87\x1f\xb5z\xd1\x96\xb6Q\ +\xe8\xcd\xed\xfcn~\xfb\xdd\xf2\xf1G\x00^<\x15\x1b\ +\x0a\xd3\xe2\xd6\xa3V\xdd[b\xabO\x9d\xdf\x9c58\ +4L_\xddL\xaf\xe7?\xdcO\xdf\xcc\x1at\x9b\xfa\ +\xfb\xd7\x1a\xdd\xcb\xd9]Cw\xfa\x8f\xbb\x86\xdb\xe9\xf9\ +\xec\xbb\xcb\xf3\xd3/\xe6w\xdf\xdc\xdf\xbc9\xbdk\xda\ +\xfc\xcb\x9b\x1fV\xa5\xde\xbc\xbd\x7f\xf3\xf6\xfe\xeby\xdb\ +l\x96\xe2\x1b\xd4k\x15,o/D=e\xae2\x98\ +/\x5c\xc1\xf4\xe2\xf2j\xbe,\xe3\xe9\xeb\x9bo\xe7\xa7\ +o.\xaf\x1b\xc8oo\x9a\x8b\xb3\xbb\xd3\x9b\x1f~|\ +5\xbf>m\x9e\xb8jq<\x9d\x9d\xdd/\x9a\xe8\x8a\ +>o\xae_\xbd\x97\xf5\x0f\xe7o\x1a;3/\xfa\xde\ +\xdb?\xd6\xdb\xbf\xfb\xed\xf9\xfc\xe2\xee\xd1\x1a\xdak\x84\ +6\xf7\xe9\x917\x0d*o\xe6gm\xa3\x5c\x01\xee\xfe\ +\xc7\xd6\x06W\xc9\xf8\xfc\x9d\xfay\xf3uc.S\x85\ +\x93\xc6\xc3A\xfb/\xae\x11\xfc\xd8\xd4^{\x03\x16\xf7\ +a\xed\xfe\xbf>{\xe6\xb0\xce`U\xec\xf4\xe6\xf6\xf2\ +\xd5eS\x13\xbc \x92J\xbaP\xab\xa3\x03!=;\ +9\xfd\xe9\xea\x87\xa6\x0b\xfd\xbe\x9d\xdd~3\xbf]\xe1\ +uw\x7fs\xf6MK\xfe\xfb\xdb\xdb\x9b\xef\xf1/\xf3\ +\xeb\xa5:\x0d\xef\xf9u\xd3\xcego\xefo\x16\x19\xb7\ +\xf3\x8b\xff\xdb\xb6)x\xfc\xf5_\xf5\xd7{\x9e\xbf\xbb\ +\xff\xf1\xaa\x81\xea\xd1\x0b\xbcxp\x02\xbfi\xac\xe8\xcd\ +\xec\xfeuK\xb2|\xae\xfd\xc5\xac\xfel\x99\xd5\xe4\xfc\ +\xe5\xa4\xed\x1f\xa1\xc0\xc9W'\xda\x5cMuq9E\ +*\xda\xc9^\xe4>\x91\xfe\xeb\xa4eP\x057\xed\xe8\ +jz\xfb\xf6\xaa1\xef\xef\xe6\xd77\xe7\xe7\xbfi\xfa\ +\xa5\x9bo\xe6/~\x05\x8b\xbf\x87\x9f\xd3\x85{{\x81\ +\x05\xde\xdc\xfff\x09\xcf\xb4\xe9\xd4o\xef_\x5c7\x9d\ +\xfao\x1ex6=\xda\xf5]\xe3\x8a\xbf\xfd\xecY\xdb\ +\xf4\xe6\xbf\x86\x22\xcfOno\xeeg\xf7\xf3_c\xc0\ +\xf3%\xc5\xd5\xe2'L\xe0\xf9\x02\xed\xd3%\xbf\xad`\ +_\xc8\x1c\x01|\xe5\xd0\x0b}\x1f\xf2\xf21\x22\xbf\x01\ +\xf8\xd1`\xffm4\xd8\x7f\xdb\x17l\x83O\x0bl\xea\ +\x82m{`\xfd\xd5H\x7f\xf2\xd5\xbe\xfe\x04?1\x7f\ +\x12\xbd\xfe\x84\x8a\xee\x03\xfch#\xffj_#\x97\xf8\ +\xb4\x8c<\x06\xe0>h\x5c\xb0\xe8\xd4e\xdf\xa0\x00)\ +\xea\xf3\x1bc\x82\x05\x8dP\xb1\xf6\xcf{\x03\x83\xf0\x85\ +\xa6\xcd\xa8d>\xbb\xfd\xd3\xed\xec\xbc5\x95\x15\xc6g\ +7WW\x0dqc>W\xdf\xcf~\xbc{\xe2\xb2\xfa\ +\x88!rc\x0e\x8d\x11\xbeY\xa9\xba6c\xda\xb0\xb8\ +\xb9\xad\x95\xd5f\xdd4\xc1\xfd\xe5\xfd\x8f/\xf0\xb1I\ +\xdc\x5c\x5c\xdc\xcd\x1b)\xf0\xac\x9aSKi\x88m\xec\ +\xb2\x07kx\x975\xae\xb3\xf6e=\xaf\xea\xf2\xbb\xdf\ +\xb6\x17\xb3\xab\xad\xe1X\x0c\xea^\xbc\xbe\x9d7\x83\xd0\ +_\xad\xf2b\x02\x7f\x82l\x95-Q\xe8\xe2\xd6\xab\x87\ +\x8c\x7f\x5c_\xde7\xa3\xcc\xb7\xcd\x90\xe3o\xed\xd8\xe7\ +?\xaf\x9b\xd1\xc7\x0a\xc5\xdf\xab\xf1~;\xbb\xbf\xbd\xfc\ +\xe1\xd7X@\x88T&\xd0\xa6\x82d\x802!,\x9a\ +\xed\xc54\xa8\x18z\xe2\xf3\x05\x9f\xb3\xc6\x00%\x0b \ +\xa6\xe32\xa71:*\xe6\xc2\xe0\xb1\xc8\xb9X\xa3\xb9\ +X\xa3\xb9m\x87\x8bNb\x98\xdcc?\xeb&B\xc4\ +4d\x22/%\x85f;\x9a\x08\x11\xcb\xb0\x89\xcc\xe5\ +\xfc\xec,\xdfa=`!Dl\xef\xb7\x90\xfa{\x84\ +\x85TX\xfa\x10\x0b\x1em!P<\x125\x17\x16\xf2\ +\xf4\x83\xb0\x80\x85\xca\x84\x8b\xa4\x13\xc5|J\xcf\x97E\ +\xc5E\xfd\xa32,\x85\xff\xd8d\xb8\x16F`\x8c%\ +\x09}\xf6,\xa8H\x88\x09-i\xa8C\xb3\xb5E0\ +\xb1>\xdb\x88]\xad\xd3Z)L\xeck\x16\xd1[\xed\ +\x17\x8b\xbf5\x8b:9\xeda\x9dk&\xb1\x13k\xf8\ +\xcd\xf1\xec\xa5\x82\xd6o/x\x00\x8f\x82H\xc29\xc1\ +\x02\xac\xc26\x99\xb6Wh\xc6\x93\xf6\x9e)\xf8\xc4\xb4\ +\xa8\x83\xf2d*YP\x08\xa5c\xe5\x89\x163\x03\xcf\xf9T&T@\x82\xa1\xc9\ +\xa7\xb63&!bi\x89\xbch \xab\xd7\x86\xc3R\ +\xcc\xdd8\x9f\x1a\x8e\x171\x22\xb7e\xce\xc5\x1a\xcd\xc5\ +\x1a\xcd\xed\xa2mi*f\x0b\xeeA\x83\x17\xd0\xe8\x0d\ +^\x1c\x0f\x80e\xa8)P\x1b\xe8f\xea\xd2\xefx\x9a\ +Q\x9b\x15\x9a\xc62A\xd4\xa2H<\x99\x12\x96d\xf1\ +\xac\xc1\x0bba2\x0ey\x0a^\xd4\x0b\x07g\xe6S\ +\xec\x82R$Y\x19\x974Tiv\x88]@\xe3\xfd\ +~\x06\x0c\xb6\xf739s[\x1fRo1\x18\xe2\xd9\ +\xf9\xf0P\xbc@R\x12\xb0=[+\xa3\xf5\xf92p\ +Xc\xa3 \x8c\xbe\xd1\xa7\x9d\x9d\xd3\xd9\xb6\xee\x12,\ +\xd6D$\xaa#\x87>\x1bPy{\x19\xb4\xbdK~\ +\x99\xd1\xc3\xfa\xa8~\x094z\xfd\x92\x8d\x0f\xf3\xb4\x90\ +9\x0b\xb6^G\x0b(\x1a\xca\x83\x07\x02uOY\x0e\ +\x07\x88\x9b\x8b\xe0\xe2\xae8\x99\x9a\x15\xe5\xd0\xe8x$\ ++\xe6j\x12O\x1e)\xb5\xb8%y\xf5Hh\x85\xd0\ +Y\xa5v\xe5RP\x90\x02;\x1e\xc9\xc5\x04v\x98o\ +\xd0|\x7f\x13#\x83\x1d\x9aX\x0d\xf3\x87\x9b\xd8\xf0s\ +\xebm\xe3\xd9Z\xe1\xfa\xda\x16\x19\xac\x1b\xbe\xeb\xb8\xa2\ +w\xb9\xef`\xf2\x00\x00\x1f\xde\xe4Is\x83\xc9\x1f\x22\ +lU\x09\xe5\x09\x16\xb1D\x94E\x0f\x01\x8a\x16\x13\xa2\ +b\xa1\x19\x13$\xa7\x92\xd2\x0e\x8b)\xb1\x08P\xb5u\ +\xd4\xe2\xe4\xe0\xfed\xeb\x1a\x05\x22-\xbc\xda\xfa\x0a\xcd\ +\xc5\x1a\xcd\xedb\xe0\x83\xc2i\xba\xc3\xdc\x9a\xfa\x91\xe6\ +\xd64\x8f5\xb7fxt{!\xf5~{9\xc4\xec\ ++1\x1a\xda\x04\x0b%\x0aac\x14X\x1c\x16YX\ +\xd0\xa3u\x8e\x82X\xc2\xa5\xb9\xc7\xa4E\xb5c0B\ +\xc5\xd0\x95\xeb\xc0\x18\x09\x8a\x09>\x8ej\xd6h.\xba\ +4u\x9c#`\x11\xbb\xcc\xbc%\x0e\xd8\xcb\xcb\xabf\ +\x8d\xceZ\x95\x0e\x0c\x8a\x17C\xf3\x9d\xf9\xc2\xe0\x888\ +u\xdb\x01N\x1b\x8e\xc1S\xa8F\xd1\x8d\xe3\xf4)\xcc\ +\xa3\xd8\xb2\xee\xd7\xa1\xc30k\xfa\x95_\xfe\xe2\x17\xbf\ +x\xd0\xb3\x16\x14\xc3\xda\xf0\xa7w\x80sv\xf6\xdey\ +\x86\x0ah\xa3e?g\xea\xe1\xfc\xfd\xeb\xcb\xfb\xf9\xfa\ +\xc0\xa9\x0b\xe8\x03\xdf\x8f\x03@M\xf3>\x005-\x07\ +\xd4\xdc\x0b>M\xc7#\xc3W\xdb\xda\x7f\xfd\xe5\xab?\ +\x7f\xf15}\xbd\x1dD\x15\xcf\x8as\x17\xffZ)U\ +\xb7n\xe9z\x0d\xee\xcb/\xff\xf8{\x80g+8\xd0\ +\x1aDU\xc9^6\x9f\x7f\xfe.\x1b\x19@d?\xe7\ +\xbd\xc4-\xbf\xee\x1b!r\xe2\x0e\x90\xa6\x14\x16\xae\xb8\ +\x22Pa\x11\xaf\xaf0\xac(\xab=\x01\x9cR\x94\x1f\ +F6\xe3\xd5@\xd8\xa0\x07\xef\xa2\x87vT\xe0\x95\xd2\ +\x87\xe9J\xe9\xc3\xf4P\xa5\xc7\x0d\xa5\xd7\x03\x94\xde\x0b\ +%\xd7\xc2k\xfb\xf3Pe\xa7\x0de\xf7]\xcan\x9d\ +\xb2K-{\x14\x00\xa0Zx[\xfe>9\xad\xb1\xcb\ +\xfe\xa5\xf7\xaf\xfb\x22\x16\xd9&b\xa9\xb1(P\x0d*\ +\x90\x0a\x83<\x05\x98\xc8YTC\x0fT\xe4\xd8Pd\ +?p\x91s\xe9y~\xdb\xc4a\xcdJ\xe4\xeb\xff|\ +\xf9\xdfM\x01\x174\xf3\xffy{y;?\xaf\x0b\xd7\ +w_\xa7\xfe\xe8L\x1b\xf9\xb0\xb2\xf8yu\xe9\xf3\x93\ +\xb6+\x85X\xf6i\xbf\xbd|\xf1\xe6\xd5\xc5\xff\x9e_\ +\xaca\xb5(\xc2\xd7\x97U\xf2\xd7\x0d\xe1\xf2\x99\xd3\xc7\ +\x87Z\xcdNW\xb869\xbf<\xa6SEb\x87O\ +\xdb\xabVE\xe8St\xab\xb5\xf8\xf2\xe9\xf9\xd5Zx\ +;\xaec\x1d_\xfc\x8d\xa5\x8f\x03\xc7j\x07\x9e7'\ +\x86\xec+\xbd`\xec\xd2\x80\xa7.%\x08\x1c\xbcS\x07\ +P\x82\xd8\xeb\xea\x8e)FA\xc2\xac\x0d\xa1\x12}X\ +\xd5\xf2\xd3T\xad\x8et\xfaGA\xce\xbb\x18\x9d\x17\xc7\ +t\xa9-\xc7K\x04\xd6\xe6\xb3F\xd2\xe40\x14\x11\xc4\ +\xd0\xbey\x8c\xce\xba\xc7\x22\x0e\x826\x9f\xb6\xcb\x1f?\ +`\x1dk\xba\x7f\xd0:\xae1\xcf\xe1\xebx\x95/j\ +\x86=E7d%U\xba1\x0ec\x01Q\xd0e)\ +/\xd6h.\xd6h\x9a@(JZ\xa0\xf2\xf0\xb2/\ +\x95\xf4\x98L\xa9\xa4&\xc7b\x1e\x1f&P\x12\xd4\x9d\ +&^L\x81\x5c'\x08%<\xc0\x9f\x0f\xd7\xc2\xd1=\ +q\x98\xfa\x11\xcd\xca\x1f\x9e9\xa2\xc8\x8f\xbbQ\x1f\ +w\xaa\x9b~\x22S\xdd\xf4IOu\xd3'<\xd5M\ +G\x9c\xea>~\xebw:Z\xeb\x9fb\x14\xe3\xa0\xb4\ +\xc9\xd4J\xb0\xb5\xe9#\xeb\xd4\x89\x5c\x0f\xe1\xd7\xa7\x98\ +%\xdb\xbf\x98Lc\x0f\x0f\xdf\xec\xbc\xbc\x9f\xdfn\xb5\ +\x1fpI\xca$\xf8\xacyn\xfe\xa7\xd9\xdb\xbb\xbb\xcb\ +\xd9\xf5\x1f\xae\xde\xb6\xcf\x0fqh\x070\xe7_\xcc\xbf\ +\xbb\x9c\xdd?\x1e\xb7\xc3)\x8a\xd2Y\x0a\xb0\xca\x94I\ +x\xb9(`)\xf9\xa8\xf5! v\x88\xfa\xa0\x22\xd0\ +\xfe\xe1|j\xbbv\xb9\x07\x1e\x18\x18\x22\xf7o\xe6\xcc\ +:f\xe1\x22\xe1Fu3\x98x\x81\x8c\xf0\xda\xbd`\ +1\xf5\xb4\xac\x1d\x0ceAb%\xdbkRa\x10G\ +\xe3\x92\x92\x16-\x88O2\xa7X\x04\xc4M\xa2\xfa\xaf\ +B\x1c\x86\x91O\x85\x9f\x22\x970\xc9\xa4G\x0d+\xd1\ +\xdeK\x1a\x982\x86\xb7\x5cDl\xde\xec\xb6\x8eC\x9d\ +\xbc\xf2\x92*Y;\x15\xe7\x22F\x16\x9d.\xc7K\xfb\ +\x1b\xa3\xea\x05\x85\xc5\x1c\x87\xd7L33?\xf9\xc9h\ +\xff\x12'\x98\xc5\xc5\xc3x\xc2X C\xd9\x9f\xef\x8d\ +\x10\x11\xf8 BL\xbc?BYB\x12\xac\x8e\xa9\xa1\ +3J-h\x0a\xd01j\x18\x02e\xc2\x85\x1f`\xa1\ +\xa2\x9a\xbc\xd8\xa9\x02\xc58\x83|2E\xcbB\x8c\x8e\ +\x13\xcbb\x0a\x969\x0a\x1d\x19F\x07e_tH\x8b\ +\x87\xa1U\xfb\xe1,d\x81\xd5~*Mg-<\x17\ +T'\x1e\xf6k\x88%\x17\x7f\x13\xd8\x1f\x05V\xe5\xe1\ +\x99\xe4\xcc\x8f\xc5F\xaa\x85@\xb5\x8cj-\x0b\xc3\xe0\ +@\xb4\x07k\xc1D\x1e\x85\x0e\x8e\xb0\x91\x8f\xcb\xc7$\ +\x1a\x89\xc6D\xa0X\xfb\xa7\x13\xa6\x02\x16\x064\x06!\ +\xc9c\xfa\x18\xa6\x0a\x8d\x14f`\xa2\xda\xafh\x09\x06\ +B\xb2\xda~\x80\x0a#\x92\x0c\xaf:v\x14\x0a[\x80\ +C\x8b\x0d\x94\xa2\xca\xea\x93)\xc9(<|\x0b\x8b\x81\ +\xbd\xbd\x8a/\x1b}\x85\x05I\x0a$j\x85%sA\ +\x13Z\xb7\x07Sq\x0ac\xd9\xab[\x9aLu\xd9\xea\ +\x96\xadj\x99k\xa30\xd2A\x9f#\x8c\xfbb\xa4\x92\ +\x05\x0cY:\x9e7\x8b\xbbXg\xc0\x85&\xc5,\xab\ +\xe5T\x9a\xcd\x18eq4\xd5\xc8\xf9\x94\x1e`B\x06\ +\xc4\xe4vh\xc3`h\xe9\x8d=aQ`\xe5q8\ +\x0d\xfaf\x17\xa2]q:t\xcfr\xa0\x1e\xaf\x17\x05\ +\x941=\xd4O\xdf\x07\x93\xb2\x8c\xf0\xc1?\xf1>\x9c\ +\x94\xc7\xf7\xe1\xbb\xece\xe6\xbe\xbd\xcc\xba\xfd\x06\xa83\ +?;?\xf7m7\xce\x89\xc6\xb8\xbd\xcc8\xfe\x04\x8d\ +>8P\x8es\x16\x00\xa3\x1dm\x7f3\xc6\xf1\xe0\x80\ +c\xc1A\xc7\x83C\xf6\xdf\xe9\xeeB\xf4\xde\xf6\xe0B\ +\xb2}{x)M\xb2\xdd7\x04\xe6Y\x9e\xcdp\xe7\ +\x0d\x81\xca\x12\xc3\xcc\xcf\xce\x9bF\x8a;\x9e4\xe1B\ +\xb6?\x9c$\x8c\xef\x85\x93\x84\xf90G%\x8c\xb7\xcd\ +\x22\xa4\xe9\xe0\xf9l\xad\x8c\xba\x85\x0cl\xd3p\x95E\ +\x92\x07\xae\x8b\xf0\xbe\xbd\x96\xc2\xb9\xc6%\x800=6\ +\xb8\xcb\xf9\xacM\xdb\xee\xe7\x14\xc1\xed=\xf1\xf9E\x93\ +\xd6\xd0\x1ca\x1c\x9c9\xe4] -\xe6\xbbz\x17\x12\ +\xc0m\x0e^9kv[\xed\xd8\x1cH\x80\xc7;\xdb\ +\xe1\x18\xb9\xcf!\x8fx\x97;rD\xf0\x03n1.\ +\xa1.\xcd\xda\xf8\xe6\xd0+\x06\x85\xb1\x1f\xa7\x1c\xc6\xe9\ +\x90\xa3\xcb\xf5\xb1\xeb\xfa\xf8\xb6o\x0c\xdc\x1d'\x1f\x18\ +#f\x84^\x8c\x92\x861\x1a;/\xb1>\xcf\xb1>\ +\x19\xb2>cR\xa7Sv\xd8\xae\x1a\xb1\xcfAq\xb9\ +\xc3>\x7f\xc3\xb88\xa7\xad\x0f\x8a\xcb\x1d6\xcb\xcf\xe4\ +\x0c\xe6\xf2\x0e\xeb\xf7y\x9a\x9e\x93F\xd7\xcf\xedd!\ +\xdd\xe1L\xf0\xa1\xf3\xc5\x87\xcf\x22\x1d>\xe1|\xf88\ +\xd4\x83/*\xce\xec3\x7f\x07\x19m\xfe\xd3\x87\xa5?\ +\x13$\x99\xac\x98;\x14\xa0\xd0\xac\x13\x09P\x10\x03\xba\ +\x93\xca\xc5CP\xb9\ +\xbep#.L\xfe\xd0z\xee\xde\xdc\xceg\xe7\x7f\x99\ +\xdf\xbf\xbeiK?\xbfh%o\x85\x15\x940\x041\ +n\x91\x8a\x82\x86\x1c\xcde\x116\x0c\xf3Q\xed\x87\x8e\ +\xdaSZq\x05\xeft\x0c\x1e%9\x99+l(\x85\ +\x5c#\xaa\xfbp)\xae\xfd\x90\x8d\xd0\x96\xf2\x88\xde\xc2\ +\xa0\x1e\xd8h\x85E\x14\xea^V\xa7\x02\x04\x81\xb5\xcd\ +\xac\xd2\x9cui\x86\xc6\xe2P4\xd0(t\x82\xc5\xcd\ +\xdd\xbc\x9d\xe0\xa4b\x19F\x92\xed\x0f(\x11\x1e\x19M\ +6k\x01\x0f\xb4\x89\x96\x14LH\x19\xd3\xca\xc8\x07!\ +\x14\xc9\x9f[Y7l\x0f\xb2\x91\xc1\xc7\xf8V\xa6\x85\ +\xd9T:\xad\x8c\x8b\x0b\x19\xc7qZ\x1ao\xe3W\xc6\ +\xb7\xb4\xb0\x12di\xb5\xa5M\xd1\x8aYH\xd43V\ +W\x89\xceV\x88\x86\xdb\x1ai\x98\x81\xb7\x91\xb8\x12\x92\ +\xb8\xb4\x97A.,2\x81\x22\x08\x06\x88\x13\x06*\xca\ +):Q(\xa0\xaa\xa9\xa3\x1a\x1a\x1e\xb5\xa1\x15TM\ +\x8dN`\x07RHW\xa2\xba\x04\x0b\xee\x8c\xe7\xa4\xe8\ +\x10\x5c\xef6%v=PO\x85\x83\xbe\xdb\x04\x0e\xb4\ +X\x842\x07\x97\x03\x9c{\x9bv}a\x97I\x07z\ +\x9b\x8b\xeb\xacG,\x07`b\x1f\xd4X\xe7\xb6\xf3\x02\ +\x08\x0a<\xday\xf7$\xb8\xbf\xc6f.C\x1a\x03\xa0\ +I\xec\xa8\xb1\x99\xdb\xb0\xc6\x88\xb3\xdcYc3\x8f1\ +\x1a\x9bU\x8dW\xd8Z\xecp\xcc\xfa\xc5\x19^\xd0\xee\ +k\x144|\xfer\xf73\xa0\x19\x83+\xf3w\xe1\x80\ +\x1dNi>\xa3\x99\xaeU\xe6\x188e\x9boE\x9c\ +\x9f\x9f\xedl@\xb2U\x939\xbfx\xb9\xbb\x01\x89\x8f\ +\xd1\x98qHc\x07v\x80\x9d5f\x1e\xd6\xf8\xe5\x85\ +\xce\x01v\xd6\x98u\x84\xc6\x22v\x94ua&\x12G\ +r\x8b&\x0ac4\x86a\xb7\x188\xe7\xf9\xce\x1a\x83\ +\x0dk\xcc\xe1\xe7ko\x87\x875\x868\xf6\xe9\xd9f\ +f}\x072\xd8A\xd6\x17p\x9a\x92\xf2\xc4J\x06\xa3\ +(\xb6\x1f5\x99z\xf1@Ik\x7f`Q5\x0cn\ +C*\xa2\x12d\x88\x93\xa9e\xb1\x0cC{\xfe\x9eH\ +\xfe\xcd\xec\xbc{\xbe\xb6\x03\xb0v\xde\x85\x01\x17\x0d\xa8\ +S;\xab$\x17\xef\x92\xdc.(TSb\xc4{\xa6\ +\xfe\xe0\xad\xcf$\x11\xea\xca\x9c:\xa5\x1cQG\x89R\ +7>n3\x9dz\xf0\xc2\x8b\xf5\x16^\xa9\x0eU\xb1\ +@J\xe6Ci\x979\x08\x98)5\x0e\xd7\x82\x91\xc8\ +\xdc]2\x03\xe0\xa1\xdb\xe8\xd5?\x96\x1b\xde\x1e\xedE\ +\xc3!-Z\xab\xe3\x00\x13\xa5\xe7\x1f\x12);\xc8\xdb\ +o(\x82\x94\xc04\x81b\x98\xac\x12\xcf\xfbq\xd9\xb9\ +n\xc8\x22b\xa5n\x1c \xf3C\xe2\xe4\xf0\xb1\xe3\xc4\ +%\x0dA*LQ\x885\xc5?(L\xf43L\x9d\ +\xc0\xad7\xa6\xab\xaf\xe4T\x0b\xb7\xba\xd6\xed\xed\xf5\xd5\ +\xcf\xda\xdd\xe5:\xb6DF\xf1\x0f\xefqM\xb8W#\ +\xa9{^\x8c\x0ap8Rw&,\x91\x1d\x1f\xd4Z\ +#i\x15\x85\xe2\x14\x19z\x10\xfb\x99\xf0\xc1\x9d\xa8\xb9\ +\xf4*\x1f\xd0\x99\xb1)\x90\xe6\xaeu\xf6\x14\x8a\x06\x98\ +x\xb5\xc1,\xc9\xc2\x5c\xd7\x91D\x14c\x96\xf8\xa8\xba\ +Q\x0a\xdc\xe5p6\xeb,\xbf$+\xc9f\xab\xcb/\ +E\xdc\x8d\xaa\xceT\x802T>\x9cFL\x8c\x87\xf1\ +Od\xc4F9\xc1\xba,O\xb3\x10i\x80w\x0fH\ +Mg\xc2Z\xf3\x95\xe6\x11\x05\xb1B\x14\xa1\xf9AQ\ +\xe0\x03\xa1\xc0L\xd4\xa2P\x12\x15R:h jq\ +3\x8f\x8a\x06\x16\x08I\xa8\x11$c\xe1\x0e\x0d-=\ +8#\xe9\x81\xc1`b\xef\x05C\xb8\x9e/hPR\ +\x94\x14\xab\xeb\x02-\xc1\xa0\xb5\x07i\x88\xaa\x0d\x0f\xe3\ +\xf8\xe1\xfc\x10y\xc0.\xed\x15\x8a\x00\x81UOeR\ +\xc0\x0d\xa5\xbe\xd4\xa845\xd4\x87\x92F\x84r\xe0\xe3\ +\xf7(\x13zO\xf1\x87<\xc0j\xda\x82 \x10\xa1\x13\ +(A\x01f\x8b\xf7\x0bf\xe2\x92\xd6^F\x183\xf9\ +\x04\x13\x8b\x88\x11O\x8c\x0a)'\xd8P\xf8\xd1\x8e\xf5\ +\x8c\x0b\x08\xbak}G/%=\x05\xa2\xbe\x93\xe4\xa2\ +\x8a\x84\xf5]\xbfb\xd1\xa4\xcc\xfa\xa19.\xe2\xe2F\ +\x0b\x80\xcf\xae.\xdf\xfcuv\xff\xba\xbd\xf9x\xbdy\ +\x12\xff\x91\x8a\x91\xda\x1d0o\x9a\xcb\xee\xd0\xbf]\xcf\ +{\xf5\xe2\xed\xed\xd5\xaf\x7f\xb5\x8a2#\xf3\xf3\xdf\xb4\ +w;\xb3\x00w\xf7\xb77\xdf\xcc_\x5c\xdf\x5c\xcf\x1f\ +\xae\xa7\x8bO\x12\xbc@+\x92\x0a\x00!\x8f7\xda\xfa\ +lj\xfe\xc5\xed\xcd\xdb\xeb\xf3n\xe6\x7f\xdf\x5c^\xaf\ +\xe6~{\xd9\xac\xeb\xbd\xbal\xfe\xf7\xe2\xe9\xf1\xf3\xd9\ +\xdd\xeb\xd9\xed\xed\xec\xc7\x07i5w9\x07\xf1B\x0a\ +\xeaC\xf6\xda\x17\xa1\x1a\xbd\xffr2\xc5\x90\xe2l\xa2\ +\x13\xf2\x92\xc2\xae'\x9f7\xb9)%]\xdd\x9er\xf1\ +dJ\xa0\x85@\xcd'\x8c%0\xd0\xad\xc9D*\xe9\ +@6\xe1,\x1a\xe4F-\x03\x22(\xe9.1\x11/\ +\x1a\x99\xc9m\xa6\x94\xd0\xd4\x89F!\x22\xc3\x87<\x83\ +$\x9fX\x14\xf7\x14\xc8\x93\xaf\xba\xd9i%\x88@\xb0\ +e[\xb3\x11\xa0\x84\xb3G\x9b\x89\x85U\xbc\xcd\x94\x82\ +\x14\xd4f\xa2\x17\x86&\xd5\xcc\x96/*\x15\xb4\x8c.\ +m\xab\xafDA\x0c\xe4.\x0b\x14)al\x5c\x85u\ +3k\xc1\xbe\xeafW5>_f\x1b\xb2\x85B\ +(Q_<\xc6\x93\xc2\x8f\xca\xf4|\xee?\xa8\x7f\xfd\ +\x86\xd3\x17\xc8_\xac\xbde\xee\xfbf}H?+\xd6\ +\xdf\x7f\x8e\x9f\xbf\xcb\xaa\xb0*\xf7\xf3\xb3~~\xf0e\ +*\xc4\x1a?\xc3\x94~~\xb1\x81\x1f\xc4\xe7\x00k\xfc\ +\x5c\xbd\x9f_\xc2&~\xfe\xfb5~\x0f.\xb7\x1a\xcd\ +.\xab\xc5\x80zv\x99\x83\xecp\x98\xd2\xf9|>t\ +\xdaI\x97\xb5\xedp\x84\x07\xcdc~\xbe\xb6rb\xc4\ +\xfa8\x83}6\xdd\x1b\x1d\xf6\xf0\x97.k\x19q\xb6\ +\xd4h8R{4N\xdf^c\xe3y\xee\xa0q\xe6\ +\xbfQc\x91\xdc\xc3\x00Dq{8`\xf1\xb7\xfd\xe1\ +b\xdc\x03\xc7\x00\xeb\x0a\xc7\xa8c\x17\xc8Y6\x1f\xbb\ +`\xd2\x9e\xba@\xc5\xda?\xef;{\x01)\x9a[&\ +\xfb\x1e\xbd\x00\xf5\xe9\xbd\xce]\xa8\xdb\x18\xa1 Ag\ +[\xa7qAM`]y\x87\xeb\x085\xf2\xa94\x83\ +\xeb{@\xb9\xfd{\xecP\x093E'\x88\xc5m\x22\ +%\x93\x84l\xcc\xb1\xc9\x1c\xc3{y|\xff\x03N%\ +\x0a!\xa1b}e\xa2E-\xba\xf0X\x96$\x86\x88\ +\x95]\x06\x9cd\x1f\x03<0\x1a\x9e\x9f\xb0\xf1x\x0c\ +\xa2\xc3\x86{\x1b\x8f\x14\x0a\xef\xc2\x93\xd8\x96\xda\xc1\xba\ +K\x0d\xea7\xf5\xdb\xfb\x1eJ\xb2\xc5fi\x82\x09\x8c\ +R\xdd\x06U\x17\x80\x9f\xa6\xea2B\xf5\xc11W\xdd\ +\xa6\x93X\x02\x14:\xc3U\xf5\x02\x1a\x8aR\x07`Y\ +@5 \x9f\xc6\x0d\xca\xc5\x18hx4V\x143%\ +'^X1\x89s>\x8d\xc94\x0a\x18\x93\xb0\xb6\xbf\ +\xb0\x90;!\xcadJP\x88\xc4\xc9\x9aK.\x99f\ +\x9c\xfb\x0f\xda\x88\x9d\x86\x9b\x8e\xc4\x9e rQ\x0f$\ +\xad{\x0b\xa5(#i\x1d\xc4b\x97\xe4\xec]\x92\xb3\ +.\xc9\x00\x90V\x14<\xc3\xf2ax\xab\x08\x89>\x99\ +\x1a\x97\x16<\xc3\xc9\x94\xa9\xa4\x03\xa2\x8eB\xec\xa8\xae\ +8\xad\xb01hg\xe5R\x14\x03\xc3\xce6B\xa6\x02\ +\x8aa\xb5\xd5\x09\x17\xcf\x04\xb2=}\xb1\x17\x1533\ +?\x8c?\xb6m6\xb6\xcb\xdeF\x85j\x1d\x9bB+\ +\x10VM\xca\xb1\xb8+Q\xdd\xad\xda\xa58\xebR\x0c\ +O\x22\x98B\xa04H\x91a*\xd0d\xda\x82\x06\x98\ +!\x13.\x9c@\x086A/\x0c\x09\xd6\xdcV/\x96\ +L\xe3\x1a\xa5\x0d;u\x8b\xdc\xdb\xa9\xb7pdp5\ +\xb1\xb4\xc2\xceH\x5c\x9d:A\x11\xf2\x1a\x0cU\x9aA\ +\x13CwR\x89\x87f\x08\xee\x84\xe1\x13+\xaaJ)\ +6\x99JIAs\xc0Q66\xec\xfd\x15\xf1\xa7c\ +c\xcc\x07\xb61\x1a\xb61\xdb\xdb\xc6(J\x0a%\xd5\ +5Jb\x85HS\xde\x99\x89\xe5\xf0\xea\xc6\x1c\x8b\x91\ +\x09\xc8\x9e6\x86%\xc4\xc8\x95\x0fec0lc\xc0\ +?w\x8e\x151\xe1m:\xc7\x9f=\x7f/~8\xd6\ +\xf3\xff\xf4[%\xc76\xad\xf2g\xcf\xdfgc\xac\xc7\ +\xf0\xfc\x87\x8c\x0a\x0e\x14\xa9\x8c\xb11\x1a\x19]\xfc\xf4\ +\x83|\xda*\xc8\xffy\xf8\xbd\xb1-\x92\x8c\x18~\x0f\ +\xb4\xc5\xe1\xa9\x97\xf1\xb3\xbb\xc0\x18\xf1\xd2\xe7\x13\xc4O\x0f\x8d\x9f\ +}\xc4\xcb%\x8e\x1fV\xa1\x1c\xe6\x9dk\xfd\x5c^\xf7\ +\x8bx\x0fN\xeb\xf5\xfc\xf2\xd5\xeb\x86\x1e\x8bf\x90\xe9\ +\x12\xce\x87=Y\x99\xf8\xb4\x85n\xb1\xfb\xb3\xa5K\xe0\ +d\xc0e\xd9\x16t\xa2\x98\xa6\xe0\x1b\x8b\xb2\xf6\xfd\xbd\ +\xfe/\xe8\x91\xbc\xf7\xeb{XX\xdc\xd4\x16\x05\xdf\xa8\ +t\xfd\xfc^\x8f\xf6\x16\xf8\x8e\xf6\xa8\x9eB]\xed\xc1\ +#\x1d\xc5pU}\x12\xf1\xa0\xae\xfaH\xc4\x89\x07S\ +\xdf\x82{>>\xc8\x84\x892^{e>PY\x95\ +\xb5\xa7\xaa\xc0\x00Xu|a9\xed@\x85\xe5\x8c\x1e\ +`%w+\xe8\xaa\xa3\x1a\xf2\x9e\x18l\xb8\x9c,\x9a\ +b\x11\xa34\x9fO\x97>\xd4h1\x0f\xe2\x854\xb6\ +\x0d\x82\xeb\xd1\x07D\xa9_\xe3\xd7O}\xc0\xd4\xc0\xa3\ +\xb0\xd6a5[\x16%\xa1\xc7\x8d\x97\xa4\x5c\x00\xa3n\ +\x0c\xae\xcf<\xf5\x0bdZ8{>O\x1e#w'\ +\xf5lNB\xd8\x7f\xa7V\xe5;{\xf1\xed\xe5\xf9_\ +\x9bM\xe7\xf7\x7fkn\xeeX\xd0a\x0eEG\xf0\x18\ +T\xeb\xcb\xc5\xdfV{,\x87{\x9e\x90\xc9\xd4\x5c\xea\ +\xcb!s\xa9\xd3\x07\x1c\xc5\x9d\xb9s\xbcY\xd6I\xf9\ +zw\xef7B\x89\xf2^\xe3\xa1D\xeb\x07\x80\xacM\ +C\xa7D\xf7\x18\x10%F?k\xc66\x0d\xb1.\x88\ +$\xbd\xfci\x83\x05\xc9y\x9b\x06\xf9\xb3\x8b\x02\x00\xf6\ +\xcb\xa0~\x19\x0am\x1a\x94!\xc4\x90\x99\xd9/C6\ +\xc8\xf06\x0d\xcb`\x8b\xcd26T\xb3I\x9b\x86e\ +H\x0e\xe8\xb1\xa1\xbe]\xdb4,\xc3@{\xf9\xf3\x86\ +\xfa\x8eY\x9b\x86\xf9;\xe2F\x1dxC}\xcf\xb8M\ +\xc32\x02e\xa3Mq\x7f}\xdf]^}7\xbf\x1d\ +\x16\x91\x88\x9bEl\xa8\xee\xf3\x8b6m!Cx3\ +T\xb1\x01\xaa\x8b&\x0d\xcbPt\x09\x8f\xe8\x95!\x1b\ +\xaa<\xa5M\xc32\x98t\xa3\x1eB\x1b\xcc\xf6\xacM\ +\xc32T137\xe8!\x1b\x9a\x9f\xb6iXF\x88\ +\x0a\x98\xf7\xcb\xd8P\xe7Jm\x1a\x94a\x10\x06\x00\xd4\ +/cC\x9d\x0b\xb7iX\x86\xa0m\xb4]\xc5~\x19\ +\x9cm\x1a\x96\x91\x14\x1aD\xfd2x\x83\x0c\x1eV\xc2\ +9}c\x85\xab\x8e\xee\xfb\x22#2\xd3\xfbe\xf8\x86\ +\x06h\xb3\x98\x0d5\x8e\x9eo\xef\x8c;\x84d8\xfa\ +\xb9]~\x92R\xc5\xac\xb3v\x91J\xa6\xad\xac]\x8c\ +\xe2NJ}\xcb\x06\xc3\xed\xfd1Mxl\xf0~\xd9\ +\xa6}c\x9a\x88M\x87ax\x9b\x06\xeb\x15$\xa5\x9f\ +?\xf5\xf3\x7f\x99m\x1a\xe4\x8f \xd9\xcf_6\xd8\xcc\ +\xbcM\xc3\xfc\x85\x09\x93\xb8_\x86m\x90\x91M\x1a\xd6\ +\x81P\xad\x9f\x7fl\xb2\xfb6\x0d\xf3\xb7\xb4M\x9dB\ +\xe4\x86z6o\xd3pl\x99\xb1\x81?\x1d\x80\x7fn\ +\x8c]#7\xc5\x95\xd2\xa4mb\xbe\xe0\xcd8m\xa8\ +k\xc16\x0d\xcbH\xd5\xcd26\x8d#\xb4M[\x04\ +\x01\x19\x91\xda\xefK\x01F\xfbkuHS\xe2~\x19\ +\xb4A\xc6\xbcM\xc32R\xa2\x9f\xff\xa6\xfa\xbeh\xd3\ +\x16\x9d\xb3n\x0e\x00`C};\xb4iXF\xf2\xe6\ +\x18\x1cb\x83\x8c\x97m\xda\xa2\x7f\xd6\xd8\xd8?#l\ +\x90q\xd1\xa6a\x19\x03>$\x91F\xda\x14\x1ek\xee\ +\xc1#\x0b\x12\x81o\x98\x810\xf7\x12\xee\x80\x87\x9c\x87\ +\x08\xa3\x9e>\xdbd\xf4`\x1e`C\xa7a6\xda\xe1\ +\x22K\xf4\xf3\x8f\x0d\x1d7\xb5i\x8b\x81#\xf7;s\ +\x87\x0d\x9d\xdelx\xe4k\x94\x1b[D8m\x10\x80\ +m\x1a\x8eX\x95bc\x8f\xe4\x1b*9\xcf\xdb\xf4\x11\ +G\xacV\x80X\xea\xd2;\xc5\x82\xc0J\xefD\xac\xcc\ +\xd1\x1b\xb1\xaa\xf4X\xbf\xda\x86\xb0\xf2e\x9b\xf6\x8eX\ +u\x83aR\xb4i\x9b\xde\x937\xf5\x0aa0\xa2^\ +G\x9fd\x97\xb2\xcf\xb9ni\xdbO\x9c\xf3\xffk\xef\ +\xca\x9a\xdb8\x92\xf4\xf3\xfaW0\xfc\xa6X\xaa\x94\xf7\ +\xa1\xf1:B\xa6\xad\x89\x89\xf0\xecN\xec<\xec\xa3\x83\ +\x92@\x8df$\xd3AQ\xbe~\xfdVv\x03l4\ +\x89&\x05\x8a\x1c\xcb\x07a\x19\xa8\xaf\xd1\xd9\x95_\xe5\ +U\x05\xa0\xcb\x8f\x85\x9e\xbf\xff]\xce\xe2\xfd\xd7\xce\x9f\ +\x9d\x9c\xe8\xd55\xf9\xdb\xdf\xe4\x8c%n\xc1\x06+\xec\ +q\xd3\xbf\xdeg\xdc\xd1\xe5\x05\xc9\xb4\xcf=\xce\xe4\xc5\ +\x9d\x92ax\x1b2\x8c\xdf\x9f\x8c\xd5\x8b\x93\xda\xc4\xf3\ +=M\x83M\x17\xd8X\x12}\x977\xc0\xe3\xb0\xdb\xd0\ +\x11q_w@\xe4\x84_\xf2~\x80\x00\xb7\xa0C\x80\ +\xf6\xb0\x0e=9\xd9\xe3~\x80 \x0bt,\x8a\xbeK\ +:\xc8nC\x07\xeda\x1d\xe5\xdf{\xd0\xc1\xb0_\xe8\ +\xb8[:8nC\x87\xc0>t\x10\xeeA\x87\xec\x15\ +I\x09\xef\x96\x8e\xe4\xdb\xd0\x91z_\xb1C\xd2\xffM\ +\xb1cY\xe7\x05\xae\x14\xf8\xe6}\xc9\xf5\x04/\xcf\xf7\ +o\xd8\x7f\x97\x15\xdecWi\xe1\x14~~\xbdd\xbc\ +*\xd9?\xc06\x14o\x93f\x15y/W9\x86\x17\ +\xefk\x1b\x8a\x0bivY\xf4\x1d\xba\x8aY\xde\x82\x0e\ +s\xbc/W1\xe7\x8f\xd5U,\xf2\xc6\xdd\xb9\xe1\xe4\ +$`OW\xb1\xac\xba\xf1\x16\x92\xe1&W\xb1\xe4\x0f\ +\xb0\x0d\xc7\xdb\xd4\x1c\x8et_\xb6\xe1(\xbf`\x09\xe6\ +\xcc\xb7\xa1\x83\xf5\xbeJ0g\xdf\xaf\x04\xbb\xe3[2\ +3`\xfe*o\xc9|\xfb\xfd>\xa6\x1d\x12\xfc\x9b\xc5\ +m\x15\xfcNvr}\x88\xd9\xb2\xfe\xea\xe6&\x0f\xa6\ +\x1fM\xc2\xd6\x86\xeaH\x8dA\xfcb\xdd\x87\xb3\xa9\x86\ +\xde\x91\x8aq\x8d\x8a\xfe\xe9]l\xf8\x10N,\x9b;\ +\x14\x04\xf6\x07\xd5\xafz\x9aqP\xfd\x10\xd6Z\xb0\xd5\ +c?\xfd\xe7;\x8a\xde\x9e\x80\xfcf1\x15x\xee\xb1\ +\xdfdJc\x99\x96\xabK\x87\x0elm\x8b\x1b\xd6\x94\ +u\xda\x147\xa5\xb7\xcd\xeeH\x0d\x84e=\x02\xf7\xd1\ +C\xb7T\xe0Y\xef\xc3t\xd6\xfbj\xdfQ\xef\xf1\x9a\ +\xde\xf3\x1d\xf4\xde\x1b%O\x9d\xd7j\xdeU\xdf\xe9\x9a\ +\xbe\xeb>}\xb7\xad\xbe\xcb\xd4\xf7h\x00@S\xe7m\ +h\x8fQ\xfd\xc5\xea\xe4\xed\xe7\x9fM{\xa2\x1d\xbfY\ +\xbd\xf8\xfe\xd5\xea\x87\x99\x22?\xbc\xfa\xf6\xc5\xe9\x0f\x0f\ +7_e\xb7\xf0Ow\x1d\xdf|\x81\x1d\x88.E\xfc\ +\xe3\x97\xab\xbe\x01S\x7f\xcf\x7f}\xba\xe3\xd0:\xd3\x0c\ +\x9fx\x8c\xea\xbe{\xd5\xb7f;}\xbd\xea\xf1\xe0\xf9\ +\xaa$n\x0e\x9c\xf5K\xec\xc0O\x9f\xfd\xb3\x13\xbd\xeb\ +\xc8\xb3\xd3\xb3\x17\xab\xb3\x8b+\xe0\x0c\x1e\xb2^\x1f\x07\ +\xab\xbf\x91\xbc\xea\xcf\x06\x1e\x8b\x83\x8b\xb1yv\xdc\xb9\ +\x9eu\xfe\xe7\xd3\xd3\x1e\xaa\xa8\xa9\x12!\xeb\xfc\xe0\xf8\ +=\xe7\xa6$pe=\xb7V\xdb\xa9\xa9G\x04\xed\xa4\ +\xb2b\x98\xc1\xceC?\x95X\xbf$\xf0\xdd\xd9Y7\ +\x8f\x87\xaf\x8f\x7fZ\xf5\x9e\x7f]O\xdf\xac\x13\xdd?\ +N\x7f(\xda*k\xbc[MPQ\xfcv\x02'Y\ +\xc3\x91\x87\xcf\x9e\x9d\xfe\xb8>\xba\x95\xeaK\xd0T_\ +\xfc\xb9\xb7\x9e\x9e\x9d\xbe\xf9\xdb\xd9\x0a\xc4\xfe\xbe:?\ +\x7f\xf5\xed\xcb2\xf3\xfa\x1b\xf3\xea\x8f?\xd5)kh\ +L\xf8\x95\x8b\xbf\xfbq\x06\xfd\xb4\x0d\xbd\xedc\xd5\xe5\ +\x14{\xcd\xcc\xfc\xf2\x81\x9f\xae\x1c\xd8\x8c\x17\x00\xf4\xfa\ +w\x0d\xae\xde|\xb7\x13\xdf26\x9a\xde\xba\x85\xca\x84\ +\xae\xaf\xd8/\xb8\xc1\xbe=~\xf6z5qY\x7f\xdf\ +\xbfz\xfb\xaa\x83#6:\xd5Uw\xfa\xfc\x93\xcf\xde\ +\xac\xce\x8f_\x1c\x9f\x1fo\xcci\xd3\xee3\xebO\xfe\ +\xe3\xb3o\xdf>~{\xf2C\x7f5\xbe|\xfd\xea\xf9\ +\xeam\xb5\xa6\xe6\xe0\xbe\x9b\xc2\xb0H\x9c\xff\xbc\x84b\ +\xfa\x11\xc9\xd4\x1c\xce\xfc\xcb\x97\x1d`4\x17&\x96\xea\ +b]\xe6\xd1t\x9d\xed\xab\xfe\xfd\xf4\xdd\xd9\xf3\xd5\x17\ +\xb5\xa3\xda\xdb[\x5c\xb2\x5c\xab;\xe2\x9b\xafW'\xe7\ +\xff3\x0c\xeeD\xcc\xfa2\xa7\xdf\x9d\xf7\xdd\xd9~\x1e\ +~\x1a\xb16\x9aI\xd5\xf3\xe3\xb3\x97\xab\xf3\x0d\x5c\xf2\ +\xe6H\xe92v\xa3\xffRb\xf5\xb4\xd7\x0a\xc7\xbd+\ +\x7f\xfb\xef?\x93\x8c\x8dbs-k\x0b\xad\x13\xaaj\ +\xea#\xda\x1d\xa5\xfb\xdf4\x82\xe3/.jPO\x8e\ +_\xaf=\xbc\x9fq\xbe:Z\x9b\xcf\xd3\xe1o\xc0\xbf\ +=\xfd\xebtdz\x7fw\x9e\xdas\xee\xf8\xf9$e\ +\xe8E\xb1<\xefG\xc1\x05N\x8a\xdeB\x7f\xb44g\ +%\xc5\xdf\x22\x11\x05/[I\x1d\x1b=\xe5\xb3\xb3\x17\ +'\x8f\xff\xf7\xcb\xa7}s\xd0\xe7\x8f\xff\xef\xf4\xec_\ +\xa3;\x16z\xfc\xec\xf4]'\xa3G\xae\x17\xcf\x1f\x9f\ +\x8c\xd7{\xf5\xa6G\xf6Go\xbf\x7f\xf9\x9f?\xbey\ +\xfd\xd9\xa3\xe9@\xbd\xa7\x82U\x9d\xbe\x11p\xb6z[\ +n\xd0\x9d\xfa\x1f\xe7\xe7\xdf=~\xf4\xe8\xbb\xbe\xa9a\ +;={\xd9\xcf\xeb\xff\xbdyU'<\xfa\xfby\xdf\ +\xd0\xf0/%wt\xfbu?\xfa\xab\x8b\xae=\xdax\ +\xf9\xe7\x9f|\xb2\xab\xd8\xee4M3\x85=\x8a\xd7\x9b\ +\xab\x82\x22~\x9c\x0f\xceg\x95\x8b\xabw\xec\xb5\x91\xd9\ +4s\xc4\xaa,w\x0ai\x80\x8aK\x920\x8c#\xe7\ +\x92bY\x92j,IR\x8d\xa3'1\x93D\xb0,\ +)M\x96$E>yz\xc4sI\xb4(\x09Y}\ +I\xd2\x17|t\xf4\xe5\xa5>\xc9\xb2$\xe7EI_\ +\xcaW\xf4U\xce%-3N\x90\xb9$\xe9\xab\xa3\xa7\ +\xf4T\xe7\x92bY\x92\xb0.Iz\xdaiz:\xb7\ +\x02\x86eIN\xb4(i\xf8\x9bK\x1a\x19\xbf\xfc\x99\ +\xf8M\x8e\x11\xfb9F\xde\xb5g(\xab\xaa\xcf5\xf1\ +\x91\x93\xddc\x8el\x86\xa2\xcb\xb6\x18O\xe2h&O\ +p#oa\xec)\xcd\x22\x96\xc7\xbf\x1es\x89\xbe<\ +jl\xbc<\xfe\xf5\x98K\xca\xbb\x1a\x7f\xc5EI\xca\ +\xb8O\x9f\x94\x17%\x85\xc8\xb2\x9f|q\x85'\xd5\x05\ +I\xb8(\x04\xbb\xdb\xf2\x5c\x88/\x19\xf6\xc2\x92\xe1\xb4\ +np\xff\x8b\x03;i\xdac\xd4\x8c\xf6e\xa8\xdcE\ +y.D\xd6\x0c\xcd\xf8\xb8\x91!\x84o\xeem\xdd\xe1\ +\xc3\x89\xc9; \xc6\xf1v\xc4\xe07\xf7\xb4\xa4\xf1\xc1\ +\xb4\xb8\xedK\x0b'\x7f\xc11\x17\x12\xb7\xa3\x85\xbe\xb9\ +\xb7\xd5\x92\xfd\x88\x89\x88'1O\xc9\xb1\x1c\xb4\x00\x9c\ +\x16%\x1d\xc5W\x91sI\xba,\xc9|1\x90>\xf9\ +\xe2\xc9\xd1\x93K\x92\xfc\x9a\x22H|I\xd2\x91\x1e\xd9\ +\x91\xcc%]\x93&\x10u\xb1\x08\xfa\xa2?\xe6\xc1=\ +\x97\xd3\x04\x03\xd2rp\xaf\xc7\x5c\xd22\xe3\x82\xd7\xa4\ +.\xef\x0f\x9bKZf\x5c\xcdsQ\xd2\x97\xf5\x98K\ +Zb\x1c\xf7\xf0\xb1\xcc\x05\xf7\xf8\xec\xe5\xe6m/\xd9\ +\xd6\xa1\xe6|\xe7\xdd\xb2\xb31\xb3\x1f\x22\xd7\xb3\x02?\ +\xe8s\xa5\xfa\xb9\xff8\x09\x9a\xd6/N_\xacj\xaa\ +\xd3\xbd\xe8\xf9\xfao\xb3\xbc\xb3\xebf\xc2\x82f\xe6\x9b\ +\xbd\x00X\x1d\x85\x0f\x1fR\xb6\xd4`\xee/\x11\x1b\x13\ +*\xcb\x83\xf9gP\x17\x9f5\xb5H\x8d\x04\xa1a\xb3\ +\xf8\xcdo\xde/\xef\x1c?\xceT\xc7\x8d\xe6\xc7\xd7\xec\ +N\x1b\x99]\xfd\xbf\x1e0\xb7\x08\x00\xe3C\xb4\x06\x9e\ +\x10x\xf0\xf56\x8a\xd9\xc0\x22\xbc\xa3\xc1M\xd36P\ +z\xdf\x15?\xa4!mA\x05\x18k\x07\xa2\x85z\x01\ +\xda\x00\x87\xb6\x04e\x89A\xf4\x96,\x87a\xcd\xd1\x81\ +\xa5\xcb\xa9\xe3\x0cY\x18\x0b\x15V\x88\xd2ah\x0b\xe6\ +9\xc0P@I\xaa\xb3\x0c\x91\x16:?\xa1\xe3\xfe\xfb\ +\xd3\xed\x1a\xd8\x86\x92f\x1a\xcc\xd9!\x9f1Tc\x01\ +\xc0XV\xa0 \xccV\x17\x99P\xc2f\xc2F\x1d-\ +:\x9cF\xc85ld\xc8a\x1b\x0a-W\x1f\x001\ +\x19\x81\x8e\x0f\x00DFIG\x82\xc6E\x91\xb7QJ\ +\x01\x0eY\x00!\x0e\xcd\xa4\x81A\xa0\xad\x16z\xf5b\ +l#\xeaB\x8f'\xf4\xe7%\xf3b\xb0\x90d\xd9\xd7\ +\xbcH\xf0\xc1\x9dY\xfe\xcd>\xb6\x19\xc1\x99\x16\xd5\xcb\ +\xb1SW?K\x9c\xd9\xbe6.\x92\xeb\x09\xa33\xb4\ +n#b\xcb\xcc\x01\xf1\xa2\x14\x80:H\x07G\xd5v\ +\xb8hk\xd4`\x8c-l\x1eY\x88\x86m\x10\xc12\ +\xd2\xa4\x96(\x87\x1e\xeb\xa1Ln\x14YmB\xac\x96\ +\x1e\xfa v\xf3z\x1c\xc5j1\x8b^\xe9\xde\xd8\xde\ +e\xd1y\x1f\x94\xf9\x83K\xc3\xbe\xcd`V\xce\xc7C\ +o\x99\xa1e\xecM\x0c\x82d\x83D\x03Bv?\x8c\ +&\x9c\xcc\xb6\x8dd\xc0\xa8\x98\xb7t\x01\x8aC\x84h\ +\xacH\xd1E\xcd\xc0H&-\xe9\xc8\xe2\xda\xa1,.\ +\xb0#\xa9\xe4\x8a\x13\xd2i\xb2\x16\x89.>\x802\x8e\ +\x1aJ\xa2\xe2\x88T[\x00\xcdhhSjra\x1a\ +\xec \x03\x86H\xcc5\x06P\x97$\xae\xd1\x82(\x13\ +(H\xd3\xc8j\xa0\x22\xdd\x0a\xf0j\x98(9_4\ +9\x1cTG\x19f\x1e\x87\xd9\x00%\x8b\xa5\x0d\x12M\ +\x9c\xd3\xa2\x00\xa2d\xee\x080#\xd3Ad\xb3H\x88\ +\x09\xf9zN\xf6\x8e\xf17\xbcq\xfc'\x13\xa8\x0c8\ +\xe5@\xe3\xc9io\xfc\xf0|\xb2\x9f\x9d\xa1\x03\xff4\ +Y\xd3\xd5\x0fz\xd7\x0e8\xef\xb8\x168Y\xd5\xe8;\ +\x95 \xd6.S\x80\xd9\x000[y\x06\xb0\x1e&\x1f\ +D\xa7\x95\xcb\xee*m\xd8\x80t\x5c\xaa1\xbc\xb2:\ +V\xf6\x82u\xc6\x81G\x9d_\xd9\xaa\xc4USch\ +\x8e\xaez\xf9\xc2?\x17S\xb7W6p\xa7\xb2\xfe\x1e\ +\xcazd\x01C\x80\xf0&\x22]\xa9AY\x19\x95\x8d\ +\xf4\x01\xb1RvH\xbb6*[\xea\x80\xd6\x19\xa5\x1d\ +!\x96v%\xee\xde\x95\xe5\x9d\xca\xe6\xfb\x8c\xacr\x01\ +,^\xca\x06t\xd5tPVK\xa1R\x7fD\xea\xff\ +\xd5\xa8W\xa3\xb2^\xca\xd6\x19\xc3\xc8\x8a\x8f#\xab|\ +{e\xafQPw)\xe88S\xf0\xf2\x85\x8f.\x03\ +\x09m\x0c_z\x90\x1d\x05)\x0e\x00;\x98\xd1\xf4R\ +7w\xd05\x072\x1a\x0dV\x1e\xb1f\xa4\xc2\xa5u\ +\xf0\xf2u\xe7\xcd\xb5\xfe\x8f^.\xa4\x82\x1e8\xbe]\ +\x8da\xff\xec\xdd\xeb\xd5\xe3\xd5\xf7\xab\x1eJ\xea\x9b\x92\ +g\xa7\xffZm\xca\x81us\xfc8\xfb1~\xf7\xe3\ +\x06(\xea\xfa\xc7\xa0\x8f\x9f\xbd;?\xdf\xc6\xfe\xd9o\ +\xc4\xd4o\xc8\xd4\xeb\x845\xba;\xa9\xb8\x1d\x12TZ\ +\xa8\xe7\x1dq\xcey\xde\xfb\xad\x83d5#\xbd\xb06\ +\x90\xc3\x8a\x9c\xeb\x17%\xea\x17\xd7\xb3&$\xf3\xf9\x87\ +\xe0\xa7;\xcbP'\xfb=\x16\xea\xf70\xe1\xf9\x05+\ +\xd2\xfb\x9f \xdeG\x05\xff\xfb\x9a\xfeL>g\x9a\xb2\ +\xf7\x08N\x1e+\xbf\xad\xc9\xc5\xcd\xd3\x03\x22\xd7\x07{\ +\x10v\x953\xcd?\xa6\x13\x1f\xc3tb\x8f\x11\xa7\xcb\ +\x13\xc2q>\xf1\xfe\xf3\x87\xad\x99\x07\xa6\xf3\x94\xfd>\ +\xc2\x19\xc0\x95\x92\xcf\xac\xa0}\xebc\xa2\xc4\xc9Q>\ +\xaa\xfa\x7fYU\xe7[\xaa\xca\x93\xaa\x1fU\xf5\xbf\xac\ +j\xc0-U\xd5I\xd5_\xe1<`\x07\x13\x08x\x99\ +\x8ak\xd4\xf7\x07\xf3R|\x9e\x0fYn\xaa\xeb?\x86\ +r\xbcz?\x15\xe4\xc37C/\xfdp\xe2\xf5\xf1\xb3\ +\xd5\xeb\xf5\xb7E\x0f\xea\xd8\xae\xaa\xad\x06\x14\xfa\xa3\x9e\ +\xd5\x1a\x90\x99\xc5\xe1\x10\xf9\xa2\xccd\xbc\xc6\xceS\x1f\ +\xe2pf\x15E\x95\xa6\xaa\xc0\xc3\xe6(&!\xab\x87\ +4\x8b\x98\xc4\xa27\xad\xc0$(\x19\x13\xfeiN\xea\ +\xa4\xf65\x1c\x1b\xc7e\x8eK\xa2if\xda{1}\ +V_\x89\xdc\xa0\x03\xed\xaf_\xf5\xa7\xc7\xb2\xc1^\x1c\ +\xf7\xef8\x9f\x9d\x1d\xff4vmD\xaf/J\x95\xf9\ +\xc1|\xa2\x1d\xde\xa2R7\x8dQ\x02 %\xbd7\x8b\ +\xf8d\xad \x89n\x08X\x183\xa7\x8f\x95f5\xc3\ ++\x91R\xb5\x0c\xaa\xd5e\x904U@\x91C\xc3f\ +,H\xd9\xb1C\xa3\xe6!`<4\xcaK\xebY\x1b\ +\xa1&\xfb\xf6Y\xd6\xd84\x1c\xd7r\xbd!\xd5U\xc6\ +k\xa2Vs\x8c\xdd\x0e\x90\xc6\x1d\xb3\x06aX)\xac\ +1\x93\xb0\x0f\x18\x19\x90V\xc2v\x0c\x1fO\xd5\xe4\xa1\ +\x8f\x89\x0dA\xc1\xea\x8d\xd2\x92\x9c\xb4\x0a@\x000\xa1\ +\x8eqS\x0e\xc2\x83\xacFO\xf1\xf5\xa2\x84#\xe9\xd8\ +\xf0h`c\xa3\xcc\xd4m\xad|d\xa3L\xe8=p\ +m\x89\x1c^\x9c\x9a\xb9\x1a\x17\xe4\x9c\xae\x5c\x9d\xa7@\ +\x1aN\x14\xb0Q\xa1\xca?\x92\xccU\xa4\x80\x93K\xd4\ +\xf433Y\xa3.(\x86^YY\x0e\x03\x8a\x8f\xaa\ +\x88\xb8\xd98\x06,\xa3\xa0\xa2v8F\xb2\xeen5\ +\xa4\x0a\xb7I\x98\xe4\xa0\x1f\xf8\xc5E\xedPa\xe0a\ +\xbbs\x8a\x1b\xc2.\x948T\x1ax\x15\x9bT-l\ +\xe4\xbf\xb8\xa8\xb3.h\x92l\xc9\xbenp6\x8bM\ +\x03+\xb8\x8e\x94\x0b\xfaa\x8d\x02:*\xd5\xd0\xa8\x86\ +Q\xc7\xb8\x85G\x10\x96\x5c\x1fz\x84<\x8e~!\x00\ +\x1c\xce\x83Q:b\xd4\xe0\xa7:S\x87\xb0\xa5\x199\ +O\xb6\xbd#>\xb3b^\x80\xe77\xecT\xda[\xe8\ +\xec\x5c\xbb@4\xc8\xf1\xc0Cl\x88\x18H\xf2`\xdf\ +U0b\x83\x07\xef\x1dK\xca\xbb\xe7\x81$\xfe-\x01\ +d\x1e(\xcai\x1c$\xab\xd0\x0dI\x95u\xd5\x8d\x8a\ +\xeaC\x89\x8c\x9c\x96X\x96Qo\xd4\xc2X\xc41u\ +pM\x17\xc5\x0e%\xb9\x12\x0c\xb3S/\x87 \x1c\xa2\ +\x87\xb1\x00\x96\x9d\xa25#\xc8\xa4\x0a \xa2\xe8f\x07\ +\xa8c8\xe7\x8a#\x96 9\xc3\xa4\xec\x88\xc9\xeb\xec\ +\x09\xd5\xc6\x08\xa8\xbe-\xd1\x9aJD\xf8\xf6\xb5\xbd\xb1\ +\x99q\xb7\xcc\xa9\x9b\x95\xd7\xc1\x19ytj\x8f\xe0\xae\ +9\x124\x84\x90(2\x1c\x12p\x80(\x0dd\x98\x09\ +\x0a\x82\xf2p\xb2z@\x14AR\xfdA(4Z\x18\ +T%ZY\xcd\x85\xb4cuq\x8e5ff>\x04\ +*\x07\x8b\x9a\xe7Lh\x11\x9d@`34\xb4\x09\x02\ +H\xccP\xcf\x86\x12\x8e:C%Z(&\xe7\x1c\xad\ +X\xc7\xe84C\xd9[ \x88\xf8\x0cEj\x94\x1c\x94\ +\xa5W\xa1\x8eF\xe5nH\xce\xa6\x07\xa9M\xc1A\xab\ +fj\x00\x11\xaa\xa5\xbf&F\xd5\x9a\x0d\xd3\x82T\xc7\ +\xd9\xb5\xb99Z\x99H\x88\xa7QE\x03\x111+\xfb\ +\x920\xa2\xdcer?\x1f\xf4\x94e\xcd`\x88\x11\x84\ +\x8d$\x08J\xa7\x09\x15j\xa2\x9aZ:Y\xb4\x94\x0c\ +\x90-\xf4\xa8PJ7\xd0-\xd4\xbc\xb9\x93J\x14\x96\ +l\x9c\xe68\xa0\x8c\xa5\xa0qKd\xe3Z\x07g\ +lL\xaa4L\xe6\xdcI\x9c\x0e8\x9aJ\xa8\xc7\x10\ +\xc29\x01\xe4@\xac\x81&\x9a_`\x5c\xf546u\ +\x0a\xc2-\xd4\xb5%\x03a\xd6u\xc80C\xc7\xceh\ +\xb6\x84p\xe2\xdbE\x98H\xdf\x7f<\x02>\xda\xf1\x08\ +k\x8e\x95\xf3\x08\x1a{\x10nc\x12-D|\xf6>\ +\xcd\xc6\x86\x149\x96\xe2\x04L0\xce\xcb-\xc0\xa3\x06\ +\x03\x0c\xd5\x86\x94i\xa0\xc8| \xda\xdc(\xd1'\xac\ +\x06=\x9a1\x0a\xdb\x16\xca\x95\x03\xd3u\xc00\xc8{\ +'I\x1bE\xb8\xd6Hr*\xa0\xfc\x06\xec(\x11n\ +aG\xf4+\xf3k\x8a\x86D\x198\xd4O\xe6F^\ +c\x14\xe2$Y\x98eP\xd8\x01k3u-$\x89\ +\xc0\xa2\xc6\x92\x1aZ\x8ax\xa1\x0eb\xce%\xcf\x15(\ +\x0bbM\xe22\x0e\x19B\xe9p]!\x91X\xcfp\ +=\x0a\xa52\xc7t\xd0\x19*\xd9\x1c\x125g\xa8A\ +\x03%\x8f\xa1\x80\xa4&\x0cY\x86\x88\x8d\x9cI\xf1\xc0\ +\xbd\x05a \x95\xb93\xa1\x99\x0c\x13HL\x1c\xf5F\ +V\x87\xf5W4P!M\xea\x9d\x14\x0a)\x07\xc1\x0d\ +B\x98b\x90\x08FA{[R-j\xec&\xcb\x9b\ +\x00)p\x99.\x90\xa8\xea\x01gC'\xd5,\x0c\x01\ +\x1d\xfd@\xa0\xa5:\x04\x0f\x98\x92\xda\xe06\xd9\xea(\ +E\xa1\x90\x0eI\xa3DT\xb2\x01cp\xd3\xabW\xde\ +m\xd9y\xf1]\x88\xfaW7\x80\xf9\xfc\xff\x01\x9e\xd5\ +\xc9%:P\x01\x00\ +\x00\x00\x18\xd8\ +\x1f\ +\x8b\x08\x00\x00\x00\x00\x00\x00\x00\xed}ms\xdbH\x92\ +\xe6\xf7\xf9\x15:\xcf\x97q\x1c\x99\xca\xf7\xccrw\xcf\ +F\xdcL\xec\xc6F\xf4\xc6]\xec\xce\xc4~\xdc\x90%\ +\xda\xd6\x8e,)$\xb9m\xf7\xaf\xbf*\x90\x12X\x14\ +-\xd0l\xda\xdd;c\xb8\xdd&\x1eTUV=\xf9\ +\x06\x14\x80\xc2\xf7\xff\xf4\xe1\xed\xc5\xd1O\x8b\x9b\xdb\xf3\ +\xab\xcb\x1f\x9e\x11\xe0\xb3\xa3\xc5\xe5\xe9\xd5\xd9\xf9\xe5\xeb\ +\x1f\x9e\xfd\xf5/\xff<\xcfgG\xb7w'\x97g'\ +\x17W\x97\x8b\x1f\x9e]^=\xfb\xa7?\xfe\xee\xfb\xff\ +5\x9f\x1f\xfd\xe9fqr\xb78;z\x7f~\xf7\xe6\ +\xe8_/\xffv{zr\xbd8\xfa\xc3\x9b\xbb\xbb\xeb\ +\x17\xc7\xc7\xef\xdf\xbf\x87\xf3\x15\x08W7\xaf\x8f\x9f\x1f\ +\xcd\xe7\xb5\xe6\xedO\xaf\x7fwttT\xc5^\xde\xbe\ +8;\xfd\xe1\xd9\xaa\xfc\xf5\xbb\x9b\x8b\xa1\xdc\xd9\xe9\xf1\ +\xe2b\xf1vqyw{L@\xc7\xcf\xc6\xe2\xa7c\ +\xf1\xd3&\xfc\xfc\xa7\xc5\xe9\xd5\xdb\xb7W\x97\xb7C\xcd\ +\xcb\xdb\xdf\xaf\x15\xbe9{UK\x8f\x9dy/C!\ +*\xa5\x1c#\x1f3\xcfk\x89\xf9\xed\xc7\xcb\xbb\x93\x0f\ +\xf3\xbej\xed\xe3\xb6\xaa\x8c\x88\xc7\xf5\xd8Xr\xb7R\ +/>\x5cT&>\xd9\x99\xe1\xe8\xba\xf4\xca\xfeu\xfd\ +\xfbP\xe1\x1e\x80\xdb\xabw7\xa7\x8bW\xb5\xe6\x02.\ +\x17w\xc7\x7f\xfe\xcb\x9f\x1f\x0e\xce\x11\xce\xee\xce\xd6\x9a\ +\xb9'\xbf\x93\xdbi\xe4\xf2\xe4\xed\xe2\xf6\xfa\xe4tq\ +{|\x8f\xb7\xfa\x9d94\xa0j\xf8\xec\xeeM\xdd\xe5\ +\x1cv\xdf,\xce_\xbf\xb9\x1b\xf7\xcf\xcf~xV\x07\ +\xcc\x8e>\xec\xdfw\xe9\xc5CC\x08\xc2\xc3\xa1{9\ +\xeb\x87t\xa3\xd6\xd9\xd5i\xedY\xed\xf7\xeb\xab\xf9\xe5\ +\xe2\xc3\xdd\xfc\xa7\xf3\xc5{\xa8\x02~\xee\x9b\xb8zw\ +w\xfd\xee\xee\xbfj\x89\xc5\xe5\xb2\xad:\xa8q\x84\xcb\ +\xc3C\xbd\x07\xb0o`\xf1\xe1\xfa\xea\xe6n\xfe\xea\xfc\ +b\xb1\x14x\xfc\xe6\xea\xed\xe2\xf8\xfa\xfc\xb2\x8e\xf0\xe6\ +\xaa\xfe8\xbd=\xbe\xfa\xf0\xf1\xf5\xe2\xf2\xb8\xd6\xb88\ +yy\xb18>9\xbd\xab\xb2n\x8fk\xe7.Nn\ +W\x9d\xbb\xbe|\xbd\xb5\xe9\x0fg\xd7U\x8d\x1e`[\ +\x0f\x7f\x1c\x0f\xff\xb1\x1e\xff\xfe\xed\xe2\xee\xe4\xec\xe4\xee\ +\xa4\xfe^\xd1z\x8fh\x0c%j\x99j\xb4/\xfe\xfd\ +\xcf\xff\xdc\xf6\x86\xfd\xd3\xd3\x17\xffyu\xf3\xb7\xba\xbb\ +\xdaZ\x81\x93\x97W\xef\xaa\x82\x86:\xabrg\xa7/\ +\xaa\xe1\xbc=\xb9\xfb\xe3\xf9\xdb\x93\xd7\x8bf\xa1\xff\xbb\ +\x1a\xca\xf7\xc7\xe3\x81\xae\xf0\xdd\xc7\xebE\x05\xbafo\ +\x16K\x0b\xdc\xe6\xb4\xf5\xbf\xb7\xe7\xad\xd2\xf1\x7f\xdc\x9d\ +_\x5c\xfck\x13\xf2\xec\xe8\xf8\xa1\x9f\xc7\xab\x8e6\xa0\ +\xed\x8e\xe3\xa8;\xf7\xc3\x1c\xf6\x1e\x0c\xa1)\xe5\xac\xf1\ +[\xd1u\xf6\xde\x9f_\x9e]\xbd\x9f\xdf\x9b\xa1G>\ +\xdb^\xe2\xden)c\xb3\xc4u\xed\xde\xed\x9b\x93Z\ +\xea\x87g\xbc\xed\xe0Uu\x8c:\x9ef\xa0\xb8:\xfe\ +\xfa\xdd\xf9\xd9\xe2\xee\xeabqsr\xd9H\xa0\xb5C\ +7U\xd4\xd6#W/\xff{qz\xb7\xfd\xd8\xcb\xab\ +\x9b\xb3\xc5\xcd\x83$\xda8pzuqu\xf3\xc3\xb3\ +\xdf\xfb\xb0\xad\x0e\xb5\xbe\xdd\x1fx5l\xcfFsy\ +yr\xbbX\xed\xde\xbe\xb9z_{U\xc1\xbb\x9bw\ +\x8b\xcd\x11\xfe|u\xf5\xb6\x0d\xcd%\xd1D\x1e\x11x\ +\xfa\xe1\x87gs\x12\x04\x16\x11\x7ft\xb4u\x96\x02\x9c\ +)?\xc5}m\x00?q\xa8\xd6V{\xdc\xe8\xbb\x9b\ +\x9b\x1a\xf4\xabW}\x5c\xdc\x8c\x01e\x1c\xcd@\xff\xed\ +\xf6\xf1\x0c\xc7\xe6/_^}X\x1d_\x99\xd9X\xa0\ +Rqo\xce\xd5J\xab\x1e\xaa_W\xac5\xf4@_\ +\x03X\x99\x1f\xc0\x9f\xceo\xcf\xab\xd3\x8f2\x87\xadF\ +\x8b\x0a\x9em\xa0-\x92\xd6\xacY;\xc0\xe0u\xbb\xfe\ +\xb0y\xe8\xe3\x96C\x8b\xb7\xd7\xab\xa3\xf5`\xf3\x97\xc1\ +\x1f\x1e\xbb\xc0\x80\x9f-^\xdd\x8e\xcan{\xec\x98\x8f\ +\xc6z]c\xeb\xf5\xe2\xb4\xa5\xc7\x95\x981\xb8.\xc7\ +\xde\x17\x95\x91\x851>_\xffW\xd3\xe0\xd1\x8b#\xd7\ +\xfa?\xdaZ\xe2\xe3\xb2\x04!\xb6\x7fpk\x99\x9f\x87\ +<\xb1\xad\x9d\xbe\x0b\xf3\xab\x9b\xf3\xd7\xe75\x88\x0f\xe5\ +tI\x94G\xab\xd3\xe9hmpZ\x1e\x02\xcc\xf75\ +\x87.Nn\xfe\xe5\xe6\xe4\xec\xbc\x1a\xd1z\x85\xfe\x88\ +h\xad\xf5\x10\x94n\xef\xae\xae\xdb\xef\xb1tCDm\ +\x94Y\xb9\xbb\xfbx\xb1X\x1e\x99\x0f\x9e\xf7\xe2\xf78\ +l\xdf\x0d\xd0\xca}_\xacW\xb9z\xf5\xeavQC\ +\x13\xd6\x0eN\x0b\x93\xcf\x17\x86[\x84\xd1\xc8\xc6q?\ +\xe8\xcf\xe5\x88\x8bMr\xc4%\x9e\xee\xb6\xcb\xa2\xbcz\ +u\x18\x8e\xb8\x94'\x85\xad\x22\xe1W\xe5\xc8q\x9a#\ +\xe7\xe9n\x1f\x8c#\xd7\xdf\x1cG\xc8\xd3\x1c\xe1D\xb7\ +O\xcf\x16\x8b\x83q\x84\xfe\xb4\xb03^\xe4\xe2\xec\x00\ +\x1c\xd5\x93\xca\xbb\xc5\xcd}\xc5\x96M\x11\x08\x19\x03\xc9\ +V\xed\x8d\xe7\xd5\xc0\xa8\xa48\xc6\x81\x8f\xcb\xf2\xac\xc5\ +\x1c\xe9\x1e\x1eO\xbc\x81\xb5\x14\xe2.0.%\x8ad\ +\x8c\x9c\xbfZ\xfc\xcb\xc9\xbb\xdb\xdb\xf3\x93\xcb\xffs\xf1\ +\xae\xf5f3\xf8\xd6a_,Nk\x93'\x17\xefO\ +>\xdev\xe4\x9c\xfdy\xf1\xd3\xf9\xc9\xdd\xfdY\xba\xba\ +\x99\x17z\xd6\xd3\xdaK\x10\xc9\xb2\xc6\xcc\xb2K\x9fi\ +5\xae\xa8SV\xe3:\xa5HLZ\xc8\xe2 V\xe3\ +\x8a\xf9\xb40\xc98[\xe8\x84\xb0Cz\x96\xd7m\x92\ +#\xf7\x89n\x97W\xa7\xf4\x8a\x0f\xe3Y\x94\x13Y\xcc\ +2\x16/m\x07a`;\xa8\xc4\x03'\xf4\x7f\xca'\ +\x96\xdb\xc4\xed\xef\xc8\xbd\xa3\xd1\x17r4scK\xce\ +\xa7\x1d\x8d>\xd7\xd1>\xd0\x0f\xcfB \x94]r\x0c\ +4\x15%\x03\x117\xd5\x07\xf4\x03\xd7\xb2\x09E\x8aH\ +\x19\xcbV\x94\x1c\xc20lD\x1f\x1b\xa7\xa9\x8c\xca\x19\ +&7^\xbc\xb9Y\xbc\xaa\xd7+\x8fR\xe8X\xee\xf5\ +\x0a\xfc\xeb\xe5\xf9]=\xc7\x7fw\xbb\xb8\xf9\x8f65\ +\xf1\x7f/\xffz\xbb~\x8e]\xa7}\xce\xfemq\xf7\ +\xe6\xaa\x8a\xad\x8d6N+\x0f\xd3#W\x08\xebG\xad\ +\xc0a\x99\x07\x1c5\xef6\xeab_k\xd4\x14\x05(\ +\x98\xb5\x1f9\x0b\x84\xa5\xf7#\xa7\x10(\x89A\xdd\xc8\ +\xdd@5\x89\xf8\xe9\x91\xfb\xc1G\xde\x97\xfaK\xbdv\ +\xbem\xb3\x14\xf5\x9a\xab\xfd\xbc\xa8\x13\x8f\x7f(1\xc3\ +\xe7;\xd1P\x1c\xd4S\xbd\xa3\xc1\x05H\xd2\xc8:\x1a\ +\x8c\x81)\xd5\xb3\xa7\xc1\x81Y\xd5\xe2\x09\x1a\xb8\x98\x1c\ +\xca\xec\xa7i\xa8\xf357\xe7\x1f\xfe@\x80\xec\xc5\xd1\ +gX\xff\x8c{sF`\xd4\x882\x9b\x13\xa4\x98\x87\ +\xf9Nde,\xb9\xeac\xc4\x1cA\x9c4\xd9\xbc\xb7\ +\x1ar\x08J\x8d\xde_\xd0A9\xcc\x9f\xa6\xcb\xff\x0e\ +\xe8\xd2\x80\xe2\xa9\xc1\xbd\x8b\xf9\xd2\xb6\xa2#K\x0b0\ +\xf5e+\x9a6pe\xe5i\xb2\xcand!\x7f\x15\ +\xb2\x0c\xeb&\x8d,/\xa2,\xb4FVk\xf0\xe4b\ +\x93\xac6\xa3DkCosH1Z\xc0Mu\xbd\ +q\xefU+\x9c\x09\x99\x142\xa2\xb5\x0a1\x81*\x93\ +tl\xf5\x22\xb9\xf8N\x9e\xe8u;\x18[\xf3Gt\ +\xa1\x9b\xfb\x8c\x99A\xc2)*Y\x0c\xea^\x16\xbe\xbd\xbb\xb9\xfa\ +\xdb\xe2\xc5e\xbd\x7f\xbd\xfa\xbd\xbc1\xf4\x02\x81$\xb4\ +\xd4\xed\x1eo\x1c\xd4\x81\xbe\xb8\xb9zwy\xb6\x0e\xfe\ +\xf7\xd5\xf9e\x8f\xd6\x9b]\x8b\x9b\x8b\xf3\xfa\xcf\x0b\xbd\ +\xc7\xceN\xea-\xa5\x9b\x9b\x93\x8f\x9d\xb0\x86\x0e\x97\x9b\ +\xb5$\x90\xad\xe0\xadW\xa5\x95\xb5\x7f;\x9a\x13\x13p\ +b\x99q\x013t\xe5\xa3?5\x94\xc1\xdc\x9cg\x9c\ +\xc0n\x85\x1b\xa6\xe0\xe2\x16\x1dfP\x98x\xbd\xfa\x8f\ +\x15\xd6{\x8b2\x06O\xe7\xb2\x84\x83\x81\x0a\xdb\xa6\xb0\ +\x10\xd0\x08\xa1\xf5\x86\xc3\xc04\xd9;\xcc!\x85Kn\ +\x08K\x01C\x97\x9980K\xf1e\xa3\xa9\x10\x85\x90\ +g\x12`\x98\x9e\xd2\x81\x05,\x8asY\xab\xaf\x08\xe9\ +*64\xea\x08\x12\xe86s\xed\x84d\x00\x89\xb9\xf5\ +22A\x19%h\x1d,\x08ZH(\xd6\xaa\x17\x82\ +\x08G\xc9\xd6\xe88\xa0\x92\xa0Z\xc4\xb25;\x8e\xbd\ +\x14\x08Q\xcc5\x8eFl\xa4s\xac\xde\x93\x1f\x06B\ +\x12\xd8\xe0QU\x9d\xb0Q\xabc\xc3\xa3\xf6Gl\xb4\ +\x93^\x16)8\x86\xe58\xb0\xa1Q\x12\x10\xe6\xf4\x91\ +\x83updkl`$\xb65+\x01\xa1\xc2R\xd9\ +\xef\xc4\x8c:\xea\xa4\x8c\xda\x1c\xc1Q\xef\xeb\xf5G\x1b\ +\xf9q\xab\xed\xff\xbc1\x13r\xbd\x8c)\xb16\xffq\ +\x1fU~\xcb!\x9d\x99@E\xedK\x84t\xf9\xea!\ +]\xcb\xde!}S\x91\x8f\x03 \x93\x80[Z\x1f\x93\ +\x98\x14\x8a\x0a\xaf\x07@\xa6\x00$\xa5\xe8\xb0\x04.\x99\ +\x1b\x01\x90u\xc5\x7f\x1f\x00\xd9\x15,\x90\xca\x8607\ +H\xd3B\xeb\x0d{@aK\xef\xb0\x02Lh\xb9!\ +,\x0c2\xa5\xf4\x01\x90#\x80<}=\x00\x8e\xe0\xe8\ +2c\xfd.\x00\xb21\x84&\xb7\x00\xd8\x09\xb9\xf7\xd3\ +^\xc6\xe8\xd2#8:\xffX\xbd\x0b\x80\xe3\x80\xba\x98\ +4\x8e}\x8c?#G#6\xd29V\xef\xc8\xef\x02\ +\xe0\xa8\xaaN\xd8\xa8\xd5\xb1\xe1Q\xfb#6\xdaI/\ +\x0b\x1dJ\x1am\x04@F\x83@\xd4.\x00\x8e\xe0\xc8\ +\xd6\xd8@\x17\x00Y\x10\x881[\x00\xec\xc4\x8c:\xea\ +\xa4\x8c\xda\x1c\xc1Q\xefc\xfd.\x00\x8ec\xea\x02\xe0\ +~\xa7B\xf2?\xeeT\xe8\xd3!\xbd\x0fe_\xfa\xea\ +c\xff\x0b\xa3'\xaf_8\xfe\xf1\xae_\x0a\x01R!\ +\xe4C$:\xca]\x13\xdd\xdf\x0f\x81\x99\x87$\xd0\xff\ +\xfe\x09\xecO\xb7\x0eL \x96\x7f<\x0b,zH\x02\ +\xe3\x1f\xd0\x02\xed\x90\x04\xd2\xae\x04~K\x22['\xb8\ +\xcbA\x93\xc8?\xe4%'\x97\xc2\xbb\x92\xf8m*\xf6\ +\x93$\xe6\x97\xb7\xc4\x03\xdeo\x9f+B\x98&\xcb&\ +\x13_KS\xdbI\xd4\xafi\x89*\xa0\xa1\xe1\xdc\x8d\ +\x8f\xd0\x80C\x95\xbb\xf1\xb9C&%Z7\xbc$\x10\ +\xb6\x08\xebF\xe9:\xf1\xb8\xc7\xf4\xed \x84 \x16\xcd\ +v\xe3\x8c\xd8\xd1\x92f\x08$j\xc1Z\x7f\x85\xa3:\ +\xc5,@\xdd\x8c\xd1f\x8c@\xa1$\x87\x08\xabR\xbe\ +\xe9\xe1\xf3\xf40'D0Q\xcdC*B\x88\xf2k\ +\xcf\x06\x12\xe9An\xf0\xf4s\x19\xfd\x98v\x9f\xcb \ +\x07-\x86\x88\xa9\xbf\xad\xfb:^\x00\x8b\x84\xcd\xd8\xc0\ +\xa5\xe0r\xa61\x08XRK\x19\xe0\x82%\xb3\x82\x02\ +\x1c\xe9(\x81\x15\x8d\x1e\xabe\xfbm3\x22\xdf\ +2\xc7\xfa\x0f\x91lK\x80\xbb'\x1d8\xd9\xf2WO\ +\xb6\x98\x07\xba\xf5F\x84\x8fr\x14\xb9\x03\xb3c\xf6I\ +\x8a\x11c9U\x952\xf6\x8a\xb1\ +\xdb\xf3\xf7\xf4{XQ\x8c\x90\x96o{\x8c{\xf3\x02\ +\x16\xe2\xe4^\x7f\x0a\x14\x09cy>\x91\xaawU\xf3\ +xR\xc1`V4\xbd?\xa9\xa0\x04R\x93\xec_\xd9\ +b@\xc50\x19O\x1f\xb6\x96=\xed\xca~:o\x17\ +7\xda\xc5\xa6\x8a\xab\xfcb\x17l\xe2\x1f7;\xf5\x96\ +}q\xb5mo\xb1O-\xc5\xb1e\xfd\x98\xefj\x0f\ +\xa7\xa5\xc5\x16i\xf4\xb44\xe7m\xd2\x0e\xb5\x16\x01[\ +\xe4\x14Kl\x89\x9f\xcfR\xc4~,\xb1%\x7f>K\ +\xa7\xf4%YrU\x9f^\xd5Bs\x1f[j\xdb\xe7\ +\xb3\xe4j8\xc1\xd2\x8e\xd2\xf0\x10,\xed\x9e\x1c&i\ +t\xe2}h|\xf9*q\x0f\x1a\x9dt\x82\xc6Ii\ +\x87\xa7Qh\x9a%\x91=\x5c\x12%p/\x96\xc4>\ +\x9f\xa5\x97\xafl\x81_\x90\xa5\xe9\xf0\xee{\x86w7\ +\xb7}X\xd2\xd8\xc7\x96\xce\xaa5})\x96\xd8\x1c{\ +\x96&:c\xe5\xb1\x81LP8\x06n\xe7\x8e\xa5)\ +iy\x86\xb8\x934\xda.M\x0fgK\xa1\xd3\xb6\x14\ +\xfe\xf9\xb6\xf4r\xcfS\x05\xf7\xc8}l\x89\xea\xf6\xa5\ +lIXbzA0)\xfbx\xdc\xe2e\xbe\xfc|\ +\x96\x84\x95~kI\x90K\xc1)\x96j\x99=r\xdc\ +\xe9\xb0\xed\xc8R/m\x9f\x1cG\xed\xcf\x97:\xa1\x12\ +d\x9a\xb4%\xe4=r\x1c\xe2.\x1e\xf7\xd9M \x90\ +\xb9\xb8\xb8~\xf7\x89e\xaf\xb6\x0e\xa0\xec`\xc4(\xb4\ +\xb5Igv\xa5\xca\xd3\x1e\x9d\xf5\x0c\x0e\x0e\xd9\xc5\x89\ +\x90'\x92\xfb\xeeb\xc5\x87?1e'\xdbW;\xdd\ +\xbeN(\x9b\xca\xde\xeb\x8eN\xafg:\xbd.\xea\xf4\ +\xea\xaa;\xae\xd3:q\x0d;\xb5\xdc\xd8\xf4$\xde\x13\ +Q\xc9\x83>o\xf6\xa0\x9f`\xea\x97\x13\x12\x82 \x0e\ +\xf5\xa9\x99\xab\x8a\x96\x04\xf2\xa0\xb0\x9d\xd6\xac!W\xb3\ +\x90\x875k\x8a\x13\xc5L\x1c0\x8bV|N\x90a\ +%\xb0<\xffU\xd9\xcc8\xe4ZX)\xa0\xc4e\x86\ +\xe0\x8ai61\x0d\xd4\xa9\x88\x09\xb0h)\xda\xa9\xa8\ +\xa2\x84T\xd1NCd@YH\xa4\xd3\x10\x07 F\ +\x9an>\xb8yPF\xeb\xf6\xd4\x03\xa8^\xf0\x90\x0b\ +\xd2\xb8y\x88\xd8\xca\x8e\x98\x95\x0b\xc6\x8c$\xc1]\xb2\ +\xcc\xe6\xa2@\x89Q>E\xf5\xf5\xc9Y7\xe1\xa5\x0c\ +\x81(\xc6\xfd\xe4\x18\x0aXb\xf7`\xf4XtD\xbb\ +\xa2[f\xe7\xbe\xac-\xa3>i\xcb\xc5\xf7\x88\x0cR\ + \x94\xca\xe6;\x95$`\x9a\xd2\xdd0\xd9V\xb6\xa2\ +s\x83\xc2b\x9c\xb1\x9b\x93\xa8\x83\xcf\x18\xc8\x16s\xfe\ +\xa2\xce/\x5c\xec)\xc2\x02e\x0f\xc2\xe6\x8eP\xd4\xd8\ +\xa8g\x0c\x0dR\xd0zG\x9d[\x01%c\xb5\x8e2\ +5p!\xc6\xf85c_\xa0\xed8\xfc\xfdoy}\ +\xa0\xc9\xdbl\x13\xa9\xa7\xbb}\xf7\xeb\xd2u\xa8T1\ +}c\xb5\xd1Fd\xf0\xf8\xa6n\x10`j\xc1\x9c\xb8\ +\x01\xdc\xdd+\xfeUY+\x87eM\x89\x0b\x0a\xb7\x14\ +KEL\xf3\x8b\xe5X\x81\xe2\x84\xdaS\x9a\xc0bE\ +\x7f]\xc7%\xfa\xc6\xe9\xc19\x95\xdf\x10\xa7V\x00\xc9\ +\xbd\xc4\xe6\x0a\xc1\x8e\x8aB\x1d\xa7.\xa0\xa6\x86}R\ +&\x02\xb1B\xfa\x84\xf3O\x9d}m.k7\xcf\xec\ +\x16\xb5\xdb\x89\xae\x03\x9c\xd8\xa8\xb0N\xac\x048.\x19\ +8\xb5\xb6`C\x8bB\xa3e\xcf\xfb\xc0[\x17I\xdc\ +\xb6F\xe2\x9c\x0c\xac_#\xf1\x80\xa4\x89jy\xe2<\ +\x5c\x85ib\xd5\xc9~\x89\xca~\x1d\xcb\x89\xb5.O\ +\xbb\xaaO\x98\xd8\x84\xbdO\xfa\x11WB\x0fI\x1a;\ +>ii\x94\x13+Nw\xabSO\xac\xdf\xdd\xad\xf5\ +=ep\xd3~X\x12I\xb1\xac\xfc\x10A\x0b\x06\x1d\ +\x8a\x9c\xe9+;\x15\xd2\xce\xa2\x8aA\xa0\x11\xd2\xc6z\ +\xb1\x08\x22\xa4\xfa\x895eO\xb7\xd6<\xedj\xfe\x92\ +\xa7t\x18\x0fl1\xc8O[\x0cn.#\xecAI\ +}\xde\xcb\x84\x10\xd1\xf2hubS\xce\xde`\xd2\xc1\ +4(\x7f\x19\x09\x04\xc9\xee\x143\x01\xd1t\xe1\xfc\x9a\ +^\xb4q\xfa\xee\x04\x94\x8e\xee='\x0clE\xc9\xbe\ +\x16)\xf3\x82P\xac\x98\xcd\xe6\xd1~\x09\x17\xf9\xaa\xac\ +ho)\x01\xa4\x18E:V\x86\xe4\x84a\x1b\xac\x98\ +B\xbaYt\xac\xb0\xe4r@\xbf\x88\x96\x12\x87s\x98\ +\xe9\x8bn\x11\xf6\x89\xa5\xed\xbbe\xf0'\x96\xcc\xef\x96\ +\xd7\xdf+\x0f}\xcd\x91\xf3\xbe\xc9\xa5\xffLB\xff9\ +\x85\xe9\x87\xf4\xd8Y\xf7y\x06\x8f\x9d}\xfa\x0b\x19\x87\ +~\xcey\xfb\xdc\x16m{l\xa7\xbf\xcf\xb2\xf7WV\ +\xfa\x9bl>\x84\x99)a`\xfb\x7fg\xa5\x17\xc78\ +-\x8ev\xfc\xca\xca\xb40>\xd8\xedQ'\xdc_%\ +\xfd\xc7\x81\xa6U\xc2{s\xd4\x7f\x1ch'a\xba\xef\ +\xc7h\xd8\xd1\xa7\xbf\x12\xd5\x7fUj\xfa+T\xfdW\ +\xab\xf6sd\xcc\x1d\xbe)\xf5U\x1c\x19\xf7\xb7\x9a\xfe\ +Cd\xd3\x8a\xc4\x9d\xacf\xff\x0f\x91\xf5\xc2\xf4`\x9e\ +eE\x7f!G\xaf\xea\xb6\x13GV|\x0f\x8e\xa6\x85\ +\xe1vay8\x8e2\x7f!G.\x8b\xb2#G\xf8\ +59\xe2\x03r\xc4\xbf4B\x0f\xdb.\x1c\xa5\xee\xc1\ +\xd1\xb40\xdc.\xcc\x0f\xf8X\x82\xe6\xdf\xd1C\x09\x8d\ +\x90\xf6A\xda\xfak\xf5\xbe\xf9\xf8R\xb9\x04h\x12\xdb\ +\x8c\x18\xc8M\xd5\xeb{\xa5\x84\x06\xa4\x89>s\x02\xd2\ +\x88\xd4\xa3?U\xd4\x81\x84\xb4\x81i\x82\xc6\x03\x143\ +g\xc8\x92\xa8~\xbf\xdfJ\xaf~\x1a\x10R1]\xab\ +\xed@\xeaEs]\x8cC\x1a35\xe1\xe2\x10\xc3\xda\ +\x16D\x06\xc9&T[\x13\x067t\xa9`\x82\x9a\xc5\ +\x11;\xc4\xb0\x97\x9c\xca\xf7\xbbR\x1bX\xfe4(\xc3\ +V+/\x01\x06\xc2L\x97#\x11\x08F\xe3Y\x01U\ +\x0f\xb4\xdc\xc2B{\x89\xb5{#\x9f\x87\xe9\xb0\xed\xef\ +\xb4>\xfe\xde\xc8\xe6;\xad\xcb\xdd\x9bw\x17\x8b\x17\x8b\ +\x9f\x16\x97Wgg\xebo\xb9V-}\xd3\xcd/\xd0\ +\x8d\x98p\xaf\x9b\xf1\xa1%6\xf5$\xd7O.\xc6\xcc\ +\xc5\xf3 \xea\x0a\x86F\x0c\xa0\xb3\xd5\x91\x05\x812\x17\ +\xe2\x86\x11G\xe4Q H:\x96\xd2 \x95\xc2\xe9G\ +^Z-\xae\xdc\x04A6j\x0b8[\xddO\x9c\x91\ +\x83z\xc6\xc3^m\xb6\xfd+\x80mH\xb5A\x07t\ +\xc2\xa8\x0d\x22\xa4\xba{\xdf\x8b\x1f\xbb\xdd\x07\xde\xee\xb6\ +=T\x91B\x85\xa2\x0c\xf3\x8a\xe3^Q #,:\ +c\x10\x13J\xe3\xe7\x9b\xecs\xf0n\x9ea*\xf8y\ +T\x7f\xb7<\xa1\x5c66~E\xf5\xf9\xaf\xac\x02P\ +TEn&\x19E$*\xed\x01\x5cH]f\x05\x8a\ +\x17+Y\xf6WEh\xbaR\x0e\xaa\xa8\xbb\x05\x91J\ +\xce\xd2!-B\xb5\x8auH\x14\x8bG\xaa\x90B\x9f\ +r\x84@%*\x19\xdf=\xa1\x1d>\x90v~\xd7\xe7\ +\xa6Zo\xd1\xf2S\x9dA9]n\x9f\x8ax\x06E\ +\x08\xb9\xb4\x18\x91\xc4Tt\x08y\x04\x81\xec\xa5E3\ +,\x5c\xdc\x86 &@li3\x170rb\xaa\xd8\ +*\xdc\xcd\x5c!\x5c9c\xa8\x9d\x18Yk\x1b\xa4\xa9\ +J\xb6H\x16`,\xadM\x22\x84\xe2:hV\x04\x94\ +L9*\xaaCH,G\x5c@0,u\xc0\x04Q\ +m\xc4\xd6j\xff\xb8\x8e\x06\x94BDC\x84\x1bQ\x03\ +\xf2\x12\xc6-d\x9a\x9aZ4_N\x0b\x13\xde>\xf6\ +m\xe8\xe3\xf0\xa7\xa1\xbd\xd6\x9f\x88ur\x90X\xc7\x91\ +`\x86\xaa3&P\xd7\xa1\xaf\xdc8C\xd3\x98\x85@\ +\xa2\x17\x1b\xc6O\x0cA\xc5+j\xa0\xe4\x864`E\ +\xc9f\xe1\x90\x9c$1TF\x14\x5c\xaej\xe2\x84N\ +\xad\xc9h\xd9\xa1\x00F\xba\xb7\xc6\x92*e\x0c\xee\x98\ +u\xc7\x86\x9d4\xca2\xec\x8dE\x7f\x1c\xf6Y\xa0p\ +a\xe1V\xb5\xed#\x84\xa4X+M\x90\x1a$\xcd\xd9\ +\xd9\x94S\xb6\x0ej\x0b8\xfa\xee\xd4\x87\xe6\x102\x95\ +J\xe1\x990\x83\xa2\xb1\xcd\xe6\x02\xe4\x81\x99\xf6\xfc\xb1\ +\x1a\xe9\xb1\x1a\xd7\x17\xa6\xf8\x84\xd7\xfa\xf3\x8de2\x00\ +#\x02K|\xd1\x0f\x06\xf4\x16\xb4\x19\x06\xe8\xd3AZ\ +\x18\xc4\x5c\xa2R\xaf\xcd[\x15\xb3\xb9\x1e\x82%1W\ +\xd4\x00M\x1cc\xddu\x1c\x98\xac\x88m8Y\x14.\ +\x9d\xe7\xa9\x80\x16N\xe5\x0eu\x87b\xac!U\x90\x09\ +\x18\x12F6\x94\x22\xbc\x96\x0d\x03'v\xd1\x16a\xb8\ +8\x16;*\x02\xe2\xca63\x07\x16\xb1\xe0\x95;R\ +\xba\x0e\xbd\x8c\xe2\xcb\x93\x18Qp-h\xdaPt\x1d\ +\xe2\x08\x93\xba\x0eCD\xe3,[\x87\xfd\xe3Vt\xd3\ +\xbf\x99\x85w=\xf5\xb4r\x10\xff\xee\xbb\xe5.\xcb\xf0\ +H\xa0V\xdc\x07\xb4\xa8\x06Y\xd3[\x90\x09S\x1b;\ +gA\x1b0\x0a\xc5\x94\x86%\x17,\xbc\x0c\x84\x85(\ +\xc4\x9a6\xb1`\x9a6\xcc\xc3E\x07L\xc2\x82\xaca\ +&m\xcc\x0d\x0b\x0eOY\xd66N]\xa1\xe2N\xb1\ +,iE\x06\xc8027*[\x89\xb0eeu%\ +\xd2\x86\x16!v\x1e0\xcc\xa1\x93\x01D\x9aKL\x82\ +\xc2K\xc3\x04M4V\x11\xdcK\x90\x0f(\x99k.\ +KJ\xf0\x00q`\xe4Fe\x09\xd5U\xdd\x8aR\xc3\ +\x8c\x0b\x97\xd2[pH\xf8\x06T4\xa8\xe4C\xe6\xd0\ +\xc2\x03j\x89.\x1b%=%t\xca%\xa8\x00\xa2\xb3\ +\xd1\xa6\xa3\xb08!\xafP)\x1e\xd1\xa1\x82\xa0\xce\x85\ +\xac5\x9b\x94\xc9+?MUod\x18hJ\xb27\ +sO\x0b'i\x98%K\xe6\xba\xa78T\xa8X\xeb\ +Ua`%$\x9e\x99Aj\xaaSm3\x12\xc4\xd0\ +\x9cf\x8ePP\x92\xf4\xc8\x11(\xd2\x88[*7J\ +B?Rjn\x8c\xe2\x83\xeb&\xb35\xcbp\x84\xd0\ +b*\xad\xa4\x17\xb7\xe5\xe9X\x11\x1b0\x02\xe1\x94\xb0\ +&[$8K\xf3hcV\xd4e\x8f\xc4\x8b\x8a\x0e\ +\xa8\xa9\x96\xaa\xf2\x86zI\x13i\xa8\xaa\x9a\x97\x16\x10\ +Z\xd78\x1b&\xaa\xc5}\x15$\x04\xa9\x8b\x12\x0d\x15\ +&\xcb\x01E\xd2hcw\x06\x0cE\xb5\x99(P:\ +\xd2\x93\x11%\x85)\xe8\xfe\xa4\x81\xa9\xb1\x1c\x19\x8c1\ +x\x1fFX\xc5\x18R\x22\x9c\xbbX\xdaP\xc7\x94\x18\ +\xd0,\x91<`\x91\x962`\xa1\x1a\x0d*HE\xbd\ +w\xf2e\xba.\xbcD\x991\x1bf\x82\x1a:`F\ +X|k\x88\xf8q\x1b\xba\xe5|\xc5w\x8fg\xfe-\ +\x9e}\x8bg\xdf\xe2\xd9\xb7x\xf6\x1b\x8eg\xc6\xbas\ +<\xdb\xe7\xfa\xeb\xfb\xe3\xdb\x9f^\xff\xf1w\xff\x1f\x87\ +I\xee\xb2\x11\x94\x00\x00\ +\x00\x00\x19\x91\ +\x1f\ +\x8b\x08\x00\x00\x00\x00\x00\x00\x00\xed}mo\x1c9\x92\ +\xe6\xf7\xf9\x15:\xcf\x971\xae*\x14\xef\x11\xf4t\xcf\ +\x027\x83],\xd0\x8b;\xdc\xce\xe0>.d\xa9d\ +kG\x96\x04In\xdb\xf3\xeb\x97\x99%\x89E)[\ +Y\x96J\xee\xde\xeb.\xb7\xdb\x95O2\x18\x8c\x87\xc1\ +\x88L2\x8b\xf9\xdd?}\xfep\xba\xf7\xe3\xea\xf2\xea\ +\xe4\xfc\xec\xfbW\x04\xf8jouvx~tr\xf6\ +\xee\xfbW\x7f\xfb\xeb?/\xf3\xd5\xde\xd5\xf5\xc1\xd9\xd1\ +\xc1\xe9\xf9\xd9\xea\xfbWg\xe7\xaf\xfe\xe9O\xbf\xfb\xee\ +\x7f,\x97{\x7f\xbe\x5c\x1d\x5c\xaf\x8e\xf6>\x9d\x5c\xbf\ +\xdf\xfb\xd7\xb3\xbf_\x1d\x1e\x5c\xac\xf6\xfe\xf0\xfe\xfa\xfa\ +\xe2\xcd\xfe\xfe\xa7O\x9f\xe0\xe4\x06\x84\xf3\xcbw\xfb\xaf\ +\xf7\x96\xcb*y\xf5\xe3\xbb\xdf\xed\xed\xedU\xb5gW\ +o\x8e\x0e\xbf\x7fuS\xfe\xe2\xe3\xe5\xe9X\xee\xe8p\ +\x7fu\xba\xfa\xb0:\xbb\xbe\xda'\xa0\xfdW\xad\xf8a\ ++~8(?\xf9qux\xfe\xe1\xc3\xf9\xd9\xd5(\ +yv\xf5\xfb\x8d\xc2\x97G\xc7\xb5tk\xcc'\x19\x0b\ +Q)e\x1fy\x9fyYK,\xaf\xbe\x9c]\x1f|\ +^\xf6\xa2\xb5\x8dS\xa2\x8c\x88\xfb\xf5\x5c+\xb9]\xa9\ +7\x9fO+\x13?\xd9\x98\xf1\xec\xa6\xf6\xca\xfeE\xfd\ +{'p\x0b\xc0\xd5\xf9\xc7\xcb\xc3\xd5q\x95\x5c\xc1\xd9\ +\xeaz\xff/\x7f\xfd\xcb\xdd\xc9%\xc2\xd1\xf5\xd1F5\ +\xb7\xe4wz\xbb\x1e9;\xf8\xb0\xba\xba88\x5c]\ +\xed\xdf\xe2\x83|\xef\x0e\x15\xa8=|t\xfd\xbe\x1er\ +\x8e\x87\xefW'\xef\xde_\xb7\xe3\x93\xa3\xef_U\x83\ +\xd9\xd1\xc7\xe3\xdb&\xbd\xb9\xab\x08Ax\x9c\x0cB\xfb\xff~}\ +rz\xfa\xaf\x83\x92W{\xfbw\xed\xdc\xbfih\x05\ +\xc6\xc3fG=\xb85s<\xbas\x88\xa1c\x8e\x06\ +~+\xba\xc9\xde\xa7\x93\xb3\xa3\xf3O\xcb[w\xf4\xc8\ +W\xd3%n\xfd\x972\xee\x97\xb8\xa8\xcd\xbbz\x7fP\ +K}\xff\x8a\xa7N\x9e\xd7\x01R\xed\x19\x1c\x15o\xce\ +\xbf\xfbxr\xb4\xba>?]]\x1e\x9c\x0d$\xd0\xc6\ +\xa9\xcb\xaaj\xf2\xcc\xf9\xdb\xff\x5c\x1d^O\x9f{{\ +~y\xb4\xba\xbc\xd3D\xf7N\x1c\x9e\x9f\x9e_~\xff\ +\xea\xf7>~nN\x0dm\xbb=q<~^5w\ +y{p\xb5\xba9\xbcz\x7f\xfe\xa9\xb6\xaa\x82\xd7\x97\ +\x1fW\xf7-\xfc\xc7\xf9\xf9\x87Aa\xa2z\x86\xdd?\ +}\xf8\xf9\xfbWKc\x08\xe7\xd0\xf2\xe0lmk\x09\ + \x8e\x92\xf1\x13\xd4\xd7\x0a\xf0'NUi5\x7fP\ +\xe9\xc7\xcb\xcb\x1a\xfb\x97\xa7\x07_V\x97-\xae\xdc\xb8\ +\xcb]\xb1\xc1\xa4[\xb7\xac\xdeV\xf9\xacc\xb4bC\ +uw4\x0c\x00+\xf3\x1d\xf8\xe3\xc9\xd5I\x1d\xc0\x8d\ +\x8b\xf1SG~\x05\x8f\xee\xa1Cd\xacY\xb0\x1a\xc0\ +0\xf0~\xf1\xf9\xfe\xa9/\x13\xa7V\x1f.n\xce\xd6\ +\x93\xd5\xef\xd7~\xfd\xd0\x95G\xfchu|\xd5:m\ +8b\xc7|`\xebE\x8d\x95\x17\xab\xc3!\xdd\xdd\xa8\ +i\xc1rm{_T\x1a\x0b-\xde^\xfc\xc7\xd0\x15\ +{o\xf6\x5c\xeb\xffh\xb2\xc4\x97u\x09B\x1c\xfe\xc1\ +\xc92\xff\x18\xe3\xfeT=}\x13\x96\xe7\x97'\xefN\ +j@\x1e\xcb\xe9\x9a(\x8f^\xa6Z\xbda\x9c\x96\xbb\ +@\xf1]\xcd\x89\xab\x83\xcb\x7f\xb9<8:\xa9\xde\xb0\ +)\xd0\x9f\x11\xadRw\xc1\xe5\xea\xfa\xfcb\xf8\xdeJ\ +\x0f\x88\xa85\x9d\x95\xbb\xeb/\xa7\xab\xf5\x99\xe58\x82\ +\xde\xfc\x1e\xc7\xcf\x1fG\xe8f\x18\xbe\xd9\x149?>\ +\xbeZ\xd5\x10\x83\xb5\x81\xf3\xca\xe4\xeb\x95\xe1\x842j\ +l\xec\xf7F\x7f-G\x5cl\x96#.\xf1x\xb3]\ +V\xe5\xf8x7\x1cq)\x8f+[G\xb4o\xca\x91\ +\xe3\xc1\xed2\x09\x97\x82_A\xe2\xe1\xe9\xc9\xc5\xff\ +9\xb8~\xbf\xa9\xf8\x16\x13\xb46\xa0n\xc1\xc9v\xb7\ +\xfb\xa9\x8bVW\xbb\xbf\xbb\xbb\x8f\xfb\xe3q])z\ +S\xd7\x90\xfe\xf0\xfb\x87\xd6\xbd\x1e\xcf.[\xe1\xab\xeb\ +\xcb\xf3\xbf\xaf\xde\x9c\xd5\xf5\xe8\x9b\xef\xeb\x05\x9e7\x08\ +$\xa1\xa5~n\xf1\x81\x83j\xe8\x9b\xcb\xf3\x8fgG\ +\x9b\xe0\x7f\x9e\x9f\x9c\xf5h]\xb4Z]\x9e\x9e\xd4\x7f\ +\xde\xe8-vtP\x97\x86./\x0f\xbet\xca\x06t\ +\xbc\xdd\xac%\x81\xec\x06\x9e\xbc+\xad\xac\xfd\xdb\xde\x92\ +\x98\x80\x13\xcb\x82\x0b\x98\xa1+\xef\xfdy@\x19\xcc\xcd\ +y\xc1\x09\xecVx\xc0\x14\x5c\xdc\xa2\xc3\x0c\x0a\x13o\ +\x8a\xffPa\xbd\xf5(c\xf0t.k8\x18\xa8\xb0\ +\xddW\x16\x02\x1a!\xb4Yq\x18\x98&{\x879\xa4\ +p\xc9{\xcaR\xc0\xd0e!\x0e\xccR|]i*\ +D!\xe4\x85\x04\x18\xa6\xa7t`\x01\x8b\xe2\x5c6\xe4\ +\x15!]\xc5\xc6J\x1dA\x02\xdd\x16\xae\x9d\x92\x0c \ +1\xb7^G&(\xa3\x04m\x82\x05A\x0b\x09\xc5\x86\ +x!\x88p\x94\x1c*m\x06\x95*\xafE,\x87j\ +\x9b\xed\xa5@\x88bnp\xd4\xb0Fg\x13\xef\xc9\x0f\ +\x03!\x09\x1c\xe0\xd6U\x9d\xb2\xd6\xab\xad\xe2\xd6\xfb\x0d\ +k~\xd2\xeb\xa2*\x8da\xd9\x0c\x1b+%\x01aN\ +o\x1cl\x82\x8d\xadVA#v\xa8V\x02B\x85\xa5\ +\xb2\xdf\xa9i}\xd4ii\xbd\xd9\xc0\xd6\xef\x9b\xf2\xcd\ +G~\x98\xf4\xfd\x7f\xdc\x9b\x09\xb9X\xc7\x94\xd8\x98\xff\ +\xb8\x8d*\xbf\xe4\x90\xceL\xa0\xa2\xf6\x12!]\xbey\ +H\xd7\xf2\x8c\x90\xdew\xe4\xc3\x00\xc8$\xe0\x96\xd6\xc7\ +$&\x85\xa2\xc2\x9b\x01\x90)\x00I):,\x81K\ +\xe6\xbd\x00\xc8z\xc3\x7f\x1f\x00\xd9\x15,\x90\xca=e\ +n\x90\xa6\x856+\xf6\x80\xc2\x96\xdea\x05\x98\xd0\xf2\ +\x9e\xb2\xa8\xe2)\xa5\x0f\x80\x1c\x01\xe4\xe9\x9b\x01\xb0\x81\ +m\xc84\xf9.\x00\xb21\x84&\xaf\x03`+\xd4\xc6\ +i\xa7\xa3\x0d\xe9\x06\xb6\xc1\xdf\xc4\xbb\x00\xd8\x0c\xeab\ +R\xb3\xbd\xc5\x9f\xc6Q\xc3\x1a\x9dM\xbc#\xbf\x0b\x80\ +\xad\xab:e\xadW[\xc5\xad\xf7\x1b\xd6\xfc\xa4\xd7\x85\ +\x0e%\x8d\xfa\x00Xa\x83@\xd4.\x006\xb0\xb1\xd5\ +*\xe8\x02 \x0b\x021\xe6\x10\x00;5\xad\x8f:-\ +\xad7\x1b\xd8\xfa\xbd\xc9w\x01\xb0\xd9\xd4\x05\xc0\xa7]\ +\x0a\xc9\x7f\xbbK\xa1\x9f\x0e\xe9}({\xb1\xbb\x8f]\ +\xdd\x18M\xdf\xbfp\xfc\xfa\xee_\x0a\x01R!\xe4]\ +$:\xca]'\xba_>\x81\x99\xbb$\xd0\x7f\x1d\x04\ +\xb6\xcb\xad\x1d\x13\x88\xe5\xd7\xe7\x81EwI`\xfc\x0a\ +=\xd0vI mK\xe0oIdr\x82\xbb\xec4\ +\x89\xfc*o9\xb9\x14\xde\x96\xc4\xdf\xa6b\x7f\x92\xc4\ +|yO\xdc\xe1z\xfbR\x11\xc24Y\xa6\x99x\xf9\ +\x9e\x9a&Q\xbf\xa5'\xaa\x80\x86\x86sg\x1f\xa1\x01\ +\x87*w\xf6\xb9C&%Zg^\x12\x08[\x84u\ +V\xba\xce<\xee1\xbf\x1c\x84\x10\xc4\xa29,\x9c\x11\ +;Z\xd2\x02\x81D-X\xeb\xb7pT\xa7X\x04\xa8\ +\x9b1\xda\x82\x11(\x94d\x17aU\xcao\xfd\xf0u\ +\xfd\xb0$D0Q\xcd]v\x84\x10\xe5\xb7\x9e\x0d$\ +\xd2\x9d,\xf0\xf4s\x19\xbdM\xdb\xcfe\x90\x83\x16C\ +\xc4\xd4_\xd6\xba\x8e\x17\xc0\x22a\x0b6p)\xb8\x9e\ +i\x0c\x02\x96\xd4RF\xb8`\xc9\xac\xa0\x00G:/\ +\xd8\xc1\xc2M\xacb\x0a\x91\xc4U<\x810\xa2\x8c\xe2\ +\x85 \xd0\x8a\xe4B\x0dPX\xc3\x1aX\x16\xc1\x10j\ +\x8eeC>\x0bx \x17\x1f\xe4- \xcd-xA\ +\xe8`EK-*\x08\xa4\xa2R\xee\xc0\xda&\x12`\ +\xca\xe8\xe5e\xad\xe8NO\xde |\xd7\x1co\x92}\ +\xcb\xc9A\xd5\xc9u\xc1\x0a\x99\x051\x86\xe9/ps\ +&\x1b\xc0B\xe4\x9c\x15T\xb8\x95\xbe\xa3\x8ds$3\ +m!\x04\xe2\xae\xe6#H\xc8J\xba\x905o\x18w\ +\xd2R \x8a\xf1\x8dn\x05v\x22\xd1\x85!(\x16r\ +k -<*\x88\xe4Mw$ )\xea\xc8\x99\x18\ +h\xd6\xcfxo\xe9\x1c\x85\xabnc0\x22\x92[\x8c\ +*\xe6\x02\xc4\xf7\x84C $3\xca\x9d\x96h`\xde\ +\xb5'n\xa5\xfb\x86;\x82\x17\xb2B\x0b\xa9\xd2\xecQ\ +\xa8\xaa.P\x9c\x8a\xf1B\x04\xa2\x14\xc1A\x9a\x80s\ +\x94\xaf\x22\x81\xb7\xf2\x0c\x8ehC\xcc\x090%\x0e\xaf\ +\xa0\x81\x97B\x12\x83\x0b\x9a!\x9bNz\xeb?\xf6\xaa\ +\x17\xab\x8e\xadb\x07\xb1\xdb\xae\xd4\x046M\x8e\x0a\x8f\ +\xad\x22\xa9\xad\x220\xa3\xc8\x0a\x16H&u[\x83C\ ++\x8b\x91\x8dS\xe5k\xc4\xed\x16\xf9\xf3\x1a\x91\xe4j\ +\x8e'\xb8\xb9\x10U\x10\x81(4i\x11\x04\x94\x14\xaa\ +Uo\xdc\xb8t0\x98\xder\xa4\x06\xaa\x86\xbc\x08\x81\ +\x82\x99\x92\x15c0\xd3\x92w\xe0 \x8dk\xb7\xec\xa5\ +% \x93\x8d\xe2N\x11W\xd0A}P\xbf\xd9$\xf1\ +\xae\xe5?\xdc \x22\xcd\x96Q0\xd4R\x067-\xac\ +\xca1\xaa\xe0d\xa1\x81\x98\x12\x96j\x15K\x88\x9b1\ +\xb2i\x0a\x02\x99Q\xe1\xa1\xbb\x884nl\xc1\x92\x88\ +>v\x02+\x1aMt\xcb\xe4\xb2\x19\x91O\xcc\xb1\xfe\ +*\x92m\x09p\xf7\xa4\x1d'[\xfe\xe6\xc9\x16sG\ +KoD\xf8 G\x91;0;f\x9f\xa4\xc8\x13$\ +0y3IQ (a\xb1\xcd,EAPH,\ +\xef\x05\xfbLHI\xf6\x96\xa66\xc1\x96\xa6Z\x05}\ +\x9e\x22S(\x99e3M\x11\x07pXx\x97\xa6\x08\ +A\x8c\xf0\x9e|\x11Pv)\xd1R\xd5-\xa8\x18-\ +[\xb5\x0a\xee[@\x02\x16\xa9e3]\x11%D\xa6\ +\xe9f\xba\x22f@\x0c\x91>c\x11\xdb@-Q\xcb\ +X7\xa0ht\x19\xabU\xd0'-\xaarE\xd4[\ +\xd2\xda\x04[\xd2j\x15\xf4\xa9\x87\x84\xc1\x99\x986\xf3\ +\x16i\x01WQ\xdfL\x5c\xe4\x08l\x0f+\x08\x84\x0c\ +\xe2\x96\xba\x1a\xd62W\x13\xbfo\x80\x05$K\xe1\x96\ +\xbc\xd6 \x16\xe5\xdcL^d\x09Jb\xd9g/\xb2\ +\x02!\x9c\xb1\x99\xbd\xc8\x19RH\xbde\xaf\xce\x8f\xfb\ +\xf4EJ\xeb\xa6\xf5\x19\x8c\xd4@2\xc863\x18i\ +\x82\xa7\x12\xb5\x0cv\x03\x0a\x8bti\xac\xc1}.\x1b\ +\xe1@\xeb\x12\x07i\x00k\xe9s\x19\xa9\xae\xfd\xbeO\ +G\xa4\xf5(Bl3\x9b\x91\x14\xf0\x0c\xa1\x96\xcd*\ +\x167n\xdb\xcb\x8b\x01\x92\xa6n\xa63\x12\x01+\x8a\ +\xd1\xb5J\xa4Y\xd0\x0ckp\x97\xd8F\x18\xbd\xd0f\ +b#QPR\xd7\x96\xd9\xd6-\xc8\x9b\xf1\xd45,\ +\x803\xacKm\x83a,(\xd2R[\xd7e]v\ +\xdb\xfe\xee\x81\xff\xfb\xdd=L\xa4\xe9\xdf\xee\x89\x9f>\ +i\x8e\xbbL\xd3s?\x0d\xe3\xa4\xd9jf~;6\ +\xfd\xab/Ny\xe4\x97c\xc2\x19\x9c\x91\xad\xc8\x8c\x11\ +\xf3\xbf\x1dk\xab\xe1L\x80B\xdc\xffZF\x08$\xdc\ +#\xfb_\xd6 \xb8k\x1aw\xab\xe3\x85!+(\xba\ +\xe5\xb3\xc3\xc0\x22\xcc\x5c\x16\x04\x85\x0c\x8b\xd2\x0e\x1e!\ +f\xb6\xed\x1e\xd3V\xdf\xc6\x11fYS\x07\xe6L+\ +\x1dmV*j\x89\xd1\xd1\x16\x90%\x84):\xdaZ\ +\xd9mis\x16\xaf\xb4\xed\x84.,\xdf\x94.\xa2\x00\ +a\xf1\xdc\x82.F\xe0\x94\x0c\x9a\xa6\xeb\xd9\xc6\xbb[\ +le\xbc\x13>\xcf\xf8\xf9\x07Y\x08\xdc\xc8\x89K\xed\ +\xd3_.]\xb63\xba\xe6c\xac\xab\xe5\x8b\xc4XW\ +\xc7Gb,\xa7\xaaR\xc63bl\x9f\x9b\xb6\xf9\x1d\ +V\x14#\xa4\xf5\xaf=\xda\xd1\xb2\x80\x858\xb9\xd7\xaf\ +\x02E\xc2X^\xcf\xa4\xeam\xbb\xb9]T0\x98\x15\ +M\xef/*(\x81\xd4$\xfb\x9fl1\xa0b\x98\xb4\ +\xcb\x87\xc9\xb2\x87\x13e\xa7\xf2vq\xdbj\xb1\xbb\xb8\ +\xca3\x86`S?Q\xed\xcc/\xd1\x8b\xabM\xfd\x8a\ +}n+\x8e\x89\xfdc\xfeX[8\xaf-&\xb4\xd1\ +\xe3\xda\x9c\xa7\xb4\xedj/\x02\xb6\xc89\x96\xd8\x12\xbf\ +\x9e\xa5\x88\xa7\xb1\xc4\x96\xfc\xf5,\x1d\xd2K\xb2\xe4\xaa\ +>\xbf\xab\x85\xe6\x13|i\xfc|=K\xae\x863,\ +m\xa9\x0dw\xc1\xd2\xf6\xc9a\x96F'~\x0a\x8do\ +\x8f\x13\x9f@\xa3\x93\xce\xd08\xabm\xf74\x0a\xcd\xb3\ +$\xf2\x84!\x89\x12\xf8$\x96\xc4\xbe\x9e\xa5\xb7\xc7\xb6\ +\xc2\x17di>\xbc\xfb\x13\xc3\xbb\x9b\xdbSX\xd2x\ +\x8a/\x1dUoz)\x96\xd8\x1c{\x96f\x1ac\xe5\ +\xa1\x83\xccP\xd8\x02\xb7s\xc7\xd2\x9c\xb6\xe5!\xd8\x91/8\ +\xc6Q\x10\x91\x17\xcbR\x00\xb9Hz\xe5\x92\xc1]\x0b\ +\xf9\xeb\xe9Y\xc2\x86\xce\xcfEN\xcf[\xf63\x94?\ +\xeb\x88\xcf\x9d\xae\x92\x92A)\xe18\xbe\xbdI\xa5\xa0\ +\x95\xb9\xd5\xa7\x17zA\xe5\x8b9\xec\xfc\x0a\x08{\xe1\ +-I\xdd\xe5\xa2\x0f\x0b\x98R\xaa/\xb4\x80\x93\x9b4\ +\x07\xde\xf6\x8db\xdb\xbfC\xac_\xb5\xf9y\x1f?z\ +rx\x98_y,\x0e\x88,\xc5o\x9e\x1bd\xb5\xa8\ +\xac\xee\xf8\xcdb?\xe7\xb3H^b\xf7o\xe5|\xa9\ +\xb7\x90\xaey\xfan\xffhu|5~\x1b\xb76j\ +\xfb\x17\xa5\x01\x93\x8b\x8d\xb91\xd8%\xa3\xeeaB\x01\ +\xa6\xc5T\x17\x8e\x10\x98\xc1\xc3\xbb\x1a\xc8\xc1\xdc\x19+\ +J\xa0$\xa5\xc8^\x8b_\xce\xa0\x96%c\x13\x93*\ +\x81Ei\x90n\xa8\x82;g\xb1\xcd\x1a\x0d\x02#\xb3\ +l\xea\xf6A\x0b\xd9\xd0\xa2\xac\xd2(9\xb6\xd3@\xd2\ +\xc7\x16\x15\x04T/1\xa0\x09H\xee\xbaWn\xf5\x8c\ +\x98d\x11\xef0^7\xa9\xd6\xb9\x81\xda\x0d\xf8\xe7\xae\ +(\xb8\x97D\xdd\xcb\x84\x12\x81\xa9\x8b\x02\x88\xe1\xea9\ +I\x5c\xddc\xa5\xdb0\xaa\xa2\xa5B\x93[\xae<\x1c\ +\x93\x0f\xb6\x5c\x19\x0f/?\x9e\xae\xde\xac~\x5c\x9d\x9d\ +\x1f\x1dmn\xc2R{\xf6\xb7\xfe\xfc\xe6\xfd\xa9\xd4\xf7\ +g{\x0e\x9fM=\xc9u\xe2\xfd\x22-\xc9\xed\xa6\x8b\ +\x13\xf4\xc6\x22\x04g\xd1\xb1;2@\x19\x8d\xd6hf\ +0\xed\xa5\x83\xa4\xc4\xdavDd\x8ej\xe7\x9d4\xdd\ +v\xe6\x0f\x83t\x06\x8fh\x01J\xabhO\xa8\x03r\ +\xf1B\x934S\xcf3\x83+[\x96\xbd\xc2\x80\x96\x9e\ +c\x9b\x14\xc3\x91&[\xff\xc3\x14:\xc5\xbel;\x9a\ +\xb2|\x1d\xd5\x7f\x5cO\x91\xae+[\x7f\x17\xc9x\xfd\ +\x13]\x10\x0c6r\xbav\xdb P\xe6B<`\xc4\ +\x11\xb9\x17\x08\x92\x8e\xa5\x0c\x90J\xe1\xf4=/\x83\x14\ +WscL\x8d\x03\xd7\xce6\x98\x8f\x03\xc3\xea\x19w\ +G\xb5\xda\xe1_\x01Lr\xad\x15:\xa0\x13F\x19\x18\ +Ju\xf7\xbe\x15?t\x87w\xe4]O\x5c\x1f\xa5\x90\ ++\xf1\x98\xb9\x97\xe3V]\xea.c\xcf%\x88\x1a\x96\ +\xd7\x0f\xa9\xb7\x9fr\xfc@%*\x19\x1b\x8e\xffp5\ +\xedE{#\x1d\xc2\x8c}\xed\x90\xa4\xe8^\xe9`\x02\ +&w\xb4!\xba\xb9`\xa6\x0cQ\xab@\x94\xd4\xac\xa8\ +\x00j\x96\x12{\x8c\x80l\x9a9D2.\x1c\xca\x83\ +4\x11E\xf8\x10\xc9$\x0a\x0a\xad\xa3\x16y\xfa\xa0\x89\ +\x10\x84s\x0c1Y\x80\x9d\x8a\x0d\xa8\x80\xa6\xa1\xed\x15\ +\x011c]cn\xa2\xd4\xb0\x0d\xe9\x1f6\xd1\x00O\ +\xe3\x18\x87SC\x152\xa4\x0c\xb1\x918s@\x04\xcc\ +]\xb8LY>5lbb\xd8LF\xa8\xdcM\x12\ +\xe2H0C\xd5\x05\x13\xa8\xebh'\x93\x02\xa1i,\ +B \xd1\x8b\x95j'\x13CP\xf1\x8a\x1a(\xb9!\ +\x8dXQ\xb2E8$'I\x8c\xc2\x88\x82\xeb\xed\xf5\ +\x9c\xd0i\xa82bA\x5c\x00#\xdd\x87\xca\x92*9\ +\x0c\xee\x98\xf5\xc0\xc6\x834\xca2\x1e\xb5\xa2?\x8c\xc7\ +,P\xb8\xb0p\x15\x1d\x8f\x11BRl(M\x90\x1a\ +$\xc3\x10eSN\x994j\x02l#o\xe6V\x1a\ +!\x93\xd0=\xeb\x18,\x09Z\xca0-!\xe0\xc8\xae\ +\xa6\x13Cp\xe2Zbs\x87\xb4\xe9HX\xe2\xf5\xbd\ +\xfd\xda\x00#\x02K\xbc\xe8\x9b\xab\x9a\x07M\x0dfz\ +d0\x17\x88\x10\xa7\xd1\xf1\x1d\xc5(\x87!\x96\x90\xca\ +\x98k\x94KJ\x8e9\x8b)m\xc4\xb2 \x9b\x8f\x03\ +\xc2os\x93\x96,l\xb7\xa1\x80\x10c\x0c\x05\xaa\x12\ +|\x1b\x0a83\xc7P\xe0\xacY\xd6\xa1 #u\x1d\ +\x0a\x8ac\xacC\x81a\x8c\x91 -\xd7\x1d\xcf`\xa6\ +D9^\xd5`a\x8aZ\xa58\xa4\x92\x86,\xbc@\ +h\x18\xe6\x9e1\x84\x8f\xf7FA\xe0L\xe1\xebL\xe0\ +\x5c\x8a6\xacJG@\x88\x16\xcd\x0d4\x0d<]q\ +,IQ\x10\xcbFl\x08\x04Qv\x92.\x8e\xa8\x00\ +\x16w\x93\x07\xd1%D\xd3\xfa\xe8b\x10\x8a\xee\xb1W\ +\x08\x92\xb9\xc4H\xa7\xa99O\xf5\xc5\xc3\xf0\xa2\x8c[\ +gezl[\xc1m\x1c\xc2\x82L\xca}\x87\xb0\x12\ +iv\xcf!,9\xf2\x9eC\xb8\x17\xf5\x07\x0eA,\ +c\x1cg\x04\xc1\x22\xa5,\x9c\xc1H\x07\x00\xd3\xad\xd0\ +\xe0!\xa2\x12\x98c!2\xc7\x8a)\x10z(\xafE\ +U\xdc\xc8\x87\x92\xae\xea6J\x07*\x8a\x0c\x18){\ +\xe1M\xc5\x0c.\x16T\x9b\xb3\xd9H\x07-\xc8>\x9a\ +\xd8\xcc1\xc8$oF\x0f\x883Qd\xa3g\xc0\xac\ +h:U\xd9\xd6\x99\x03*^\x9c7;\xdd!\x84\x91\ +6\xa1\x04wM.\x0f|F\xc9\xd0\x1f\xf8\x0c\xa6\xe7\ +}\x9f!q\xa5\xa9\xfez\xe04\x82\xea\xdb:M\xe4\ +\x13\x9c\xa6\x10H\xb0\xd9\xdas\xc5\xb4\xf8\x9a\x95\x12%\ +\xd6\x8c\x92\x07\xa5\xef\x15\x06\x11*4bE4T6\ +1\x87\x0cE\x1a\xa2HC\x95\x81KP\xf2v\x03\xac\ +0\xa9\x8dd\x93\xa8\xae]6\x0aI\xe1\xc9v\xfe0\ +\x85\x8e{\xb12C\x86\x14\x1c/J\xb2\x84\xd9\xe8\xcc\ +\x02\x16\xa1J}(\x0aH\xd7(](\x12\x88\x94H\ +\xdb\x080\x81`\xe9B\xde\x87\xa2\x04CB\xe2\x0d4\ +\x1d0L}\x1d\x8a\x0a\xabQ\x1f\x8a\x14\xdd\xa2\xa3\xc4\ +\x0b(\x19\xf3\xd0\xa04\x90 \xb6\xb1\xa8\xb0\x15.C\ +\xc4\xc3\x08\xcc\x1c\xb0d\xe6b{\x9e`R<\xa8a\ +U\xda\x18\xd8\xccs\x13\x15\x87\xa4`-\x83\x1eT\xcd\ +\xe0i\x8a\xa6\xd0\x09\x9f4\xdd\xda'm\xd6'\xbf\xdb\ +\xbf\xfa\xf1\xdd\x9f~\xf7_\xea_?\x7f\x00\x9a\x00\x00\ +\ +\x00\x00\x15,\ +\x1f\ +\x8b\x08\x00\x00\x00\x00\x00\x00\x00\xed=ko\xe3F\x92\ +\xdf\xe7W\xf0\x9c/1N\xa4\xfa\xfd\xf0xf\x81\xdd\ +A\x82\x00\x17\x1c\xb0\x9b`\xf7[@K\x94\xad\x8b,\ +\x1a\x92<\x96\xe7\xd7_U\xf3\xd5$[\x14\xe5\x91'\ +A`;\x89\xa9\xea\xeaWu\xbdY\xad\x5c\xffm\x7f\ +\xbf\x8a>g\x9b\xed2_\x7f\xb8\xa0\x09\xb9\x88\xb2\xf5\ +,\x9f/\xd7\xb7\x1f.~\xfd\xe5\x87\xd8\x5cD\xdb]\ +\xba\x9e\xa7\xab|\x9d}\xb8X\xe7\x17\x7f\xfb\xf8\xee\xfa\ +\xbf\xe28\xfa\xc7&Kw\xd9_o]\xcf\xf5\xf6;\x0fy3_\xd4\xd8\ +\xb8\x9a'\xee\x90\xa8\xb5vJ\xd8\x94\xb1\x180\xe2\xed\ +\xf3z\x97\xee\xe3vWXc\xa8+#\x84L\xa1\xad\ +\xc1\x1c\x87u\xb5_\x01)\x0e.\xc6\xb5\xfa\xb3\x03\xf9\ +\x1f\xe0\xdf\xbaC\x05H\xb6\xf9\xe3f\x96-\xa0g\x96\ +\xac\xb3\xdd\xf4\xd3/\x9f\xea\xc6\x98$\xf3\xdd\xdc\x1b\xa6\ +\xa2~k\xde\xd6\x91\xac\xd3\xfbl\xfb\x90\xce\xb2\xed\xb4\ +\x82\xbb\xfeO\xcb\xf9\xee\x0e\xb8\x81\x19\xf7\xf1.[\xde\ +\xde\xed\x9a\xcf\xcb\xf9\x87\x0b\xd8\x1f\x17\xa4\xf8\x5c\xad\xe0\ +\xaa\xe6#\x92pV\xa0\x96\xc3\xfaM\xc2$4\xdaX\ +\xad\x88Ci1_k\xb8y>\xc3\x15~\xb8xH\ +o\xb3\xf8K\x9e\xdf'0\xed\x97\xf6\xc0\xf9\xe3\xee\xe1\ +q\xf7[\xb6\xdfe\xebb\x1c\xd8\x99\xb7M\xd7\xec\xfa\ +%\xad=\xd6\x03d\xfb\x87|\xb3\x8b\x17\xcbUV\xcc\ +6\xbd\xcb\xef\xb3\xe9\xc3r\x0d\xfb\xde\xe4\xf00\xdbN\ +\xf3\xfd\xf3m\xb6\x8e\x973`\xb5)\xf4[\xa57\xab\ +l\x9a\xcevK\x07\xb8OW\xab\xa90{a\xa6\xdb\ +u\xfa\x10\xc3\x80y\xf2\xb0\xbe\x0d\xce\xb4\x9f?\xc0\xd1\ +R.\x83\xad\xcfu\xebGh\xbe\x9eg\x8b-\xa2\x15\ +T\xc7O\x5cP\xe2\xda\xa0\x15\x18'K7?n\xd2\ +\xf9\x12\xc4\xa5\xc0\xf3\x86\x9c\xe5\xabU6\x83\x93KW\ +O\xe9\xf3\xf6\xa2F\x80\xa1\xda]\xb9\xa5\xaa\x1c\x14\x86\ +\xdd\xee\xf2\x87\x0a\x17\xcec\xf7\xbc\x02\xb2 0\x86\x11\ +\xf3\xcd\xd5w\xf4\x86I\xce\xdf;P\x0e\x0c\xb4\xdc=\ +_\xd1\xf7\x17M\x9f|\xb1\xd8f01\xf1`\x8em\ +\xa0\x07\xcc\x05Je\xfau\xb3\x91\xd0l48\x1b#\ +\xf5l\xd7\xd3\xf6\xb6\xbf\x8e\x8cNn\xaf\xee6\x19\xe8\ +\x99\xef\xfe\xf3\xf3\xff\xfc\xf4\xe97\xfb[\xac\x06\xc8\xcc\ +\x0c5\xa2n\xbf-\xa1\xbf\xae\x97;\xd0$\x8f\xdbl\ +\xf3/\x94\xc6\xff]\xff\xba\xcdzX\xbfl\xd2\xf5\x16\ +D\xff\xfe\xc3\xc5\x0e\x1fW\xa0|\xbfg6\xb1\x8a+\ +i'1\xb0L\xa2\x14\x93\x97\xcd\xfa(\x90DX\x90\ +E\xab\x1a\xd2<\x03\xd4\xc8\x84s!H\xb3\x96=\x03\ +\x5c)p\x04\xe3\xe12\x1f\xb7\xa6\xe2\x01z\xc1v\x03\ +T\x18\xb7K\x5c\xab\x15\x09\x17\xdc\x10\xdeZ+%\x0c\ +\xc0B\xb7\x96jT\x22\xb9\x94\xd6\xb6\x96\x0a\x03H$\ +\x869\xc4\xca!\xb6\x0c0\x9c\x9c\xe1\xef\x00{W\xcc\ +\xa5X,\x0f\xb3r\x85\xa5\x85f\xf1\x91)\xb3\xc5\x82\ +d\x8b1\x12\x95p\xca\xadPz\xdc\xc4$\xa6\xc3\x13\ +\xa7iz\x93\xdaQ\x13k\x06ZB){x\xe2\x90\ + \x86\xe8\x0bG'\xe9\x18\xfa\x0a\x8f\xbe\x7f\x06\xe1=\ +\x95\xad\x03\xc2{\x9f\xee6\xcb\xfd\xf74\xb1\xf8c\xe8\ +\x84\xc0\xaf*>Y\x09r\xccXB8cz\x12+\ +NA -\xb3\xdf@\xa6q\xa1\xe9\xeaU\xc8\xd8\x1e\ +\x1a\xc9h\xceFF\xa0\x95\xfb1\x8e\x8c\xe5'\x0aT\ +\x14\x0a\xf6\xcd)>R\x0d\xbb\xe5\xba!\xe2l\x8fD\ +D\x04\xe6i\x90\xd93\xba8\x04\x87\xa0\x0dt\x11\xc4\ +]\x04q7\x1f.\x90\xb0\xe0\xfb\x1cS\x95'\x91\xb5\ +=\x86\x16\x96\x0ds\xa9%_O\xde\x03&\xc6 \xe3\ +Q\xde\xe5G\x9dP\xaa\xba\xdc\xa8\x8d\xe6\xa6\xcb\x8d&\ +\xd1\xd2\x18\xde\xe2F\x0aS0nGX\x98\xd3\x1d\x1b\ +G\xad\xf1\xae\xc6\xc2\xfd\xbc\xd0\xb1\x81\xb9\xc4I\x8eM\ +h\xb6\xd1\x8e\x0d\xcc\xa6\x8e\xe9\xc6s\x08u\x87\x9e\x94\ +\x9aa\xe1\xf6\xb8s\x1c\xf7\xa14\x1a\x9d\xc8\x96 \x82\ +3c\xb4$\x9e6CA461\x82\x1a.[\x82\ +\x08\xeaPjmmK\x10)\xa0j\xc6H\x7f5}\ +%B@\xfbRI\xb8\x14\x13\x88\xa2('\xe0n\x10\ +>)\x1fQ\xb7 \x82\x90\x0a\xb4\x09O,W\x94q\ +>\xa1\x14,\x22\x01\xddz9\xc63\x0a\x90q<\xa3\ +\xcc\xf9b6\x8aQ\x82lI=\x0f\xf8\xb0\xafPE\ +w\x9d\x01\x13)A\x8b\x12O\x90\x83+\xbcI\x17r\ +\xd1q\x99H\xc2\x80\x8e \xee\xec\xfd\xa8\xf9Up~\ +-\xa5b\xd4;\xc7\xe0\xfc\x86\xdf\xc8\xec\xa6;\xbf\xb0\ +\xe0MZ64\x7f\xd0=\xd1\x8b\x99\xea\x8e\xa5\x8d\x10\ +\x86i3Z>\xc1\x90\xff\x01\xf2\x09+\x1c\x90O\xcd\ +\xbc\x83tfP\x83\xee\x06\xcbF[\xd2'X\xa2\xa4\ +\x96\xac%|}\xd4E\x08\x15d\x8f\xb3DR\xa6U\ +?\xd4\x09\xf9A\x1a\xf8\x8b\x17\xe2f9\x08\x86\xd0N\ +\x0a-D\x02 r\x13\x87`\x15\xe3\xe8\x1aQ0\x14\ +\xda\xc0\xa3\x92\xc0\x98`\xd9/G*\x9bW\xb0,H\ +\xe9\xf1LU\xb8)/\xb5,\xcc\x0c\x88\xf0\xc8\xd9\xc6\ +[\x16f\x8er\xeeb\xb9\xdae\x9b\x9agp\xd6x\ +\xb9\x06\xd0C\x0e^\xc32_\xc7\x05\x06\x9c\xc4\xf6\x9f\ +?\xfe\xfd\xe2$B\x17]a\x19^Nb\x91\xfd\x98\ +>n\xb7\xcbt\xfd\xf7\xd5\xe3\xc6[\xf4\xb1\x01\x91>\ +\xf3O\xd9\xe7\xa5[\x16j\x14\xa54\xea_\xdd\xd9z\ +{\x06\x98\xddxD(\x96\xf4jb\x0bq\xaf\x8a\xb9\ +\x17$\x06DW\x04l\xd9\x8b\xa3\x0f\xe7.S\xf4_\ +\x85!\x0a\x9eA\xe4\xc0\x00\x1a\x10.\xd1q\x94-8\ +\x80V\xd3\xb6\xa3lUb\x94\x91T\xb6UD\x0fw\ +\x11\xc4m\xe9\x88\xf3\x8bfM\xcdo\xe4\xf8a\xd6\x22\ +\x96\xf1@`|n\xe7\x0f\x837\x98\xf1hf\xeb[\ +\x88\xa9\xa0\xaf!\xa6\x10\xca\x19\xcb\x055\xc3R*\xe8\ +A)=_b\x80\x92\xdfb3\xc0o\xe8\xf5\x9e\x9a\ +\xd6s\x81\x13X1%\x8c'\x18\x18:q\x9epb\ +\x98\xe7\xe4\xba\xd0\xc9\x9a\x84\x1aF<\x5c\xe6\x1cb%\ +\x8d\x1a\x9f\x9ako\xe5\x84\xdc\x5c{\x8d\xd4K\xd2U\ +\xf98\xa3\xa4\xa7\xa1\xaa|\x5c\x01\xfd\xba|\xdc\x0f\xee\ +'\x94\x1f\xb2\xb1\xfa\xfal\x94\x04\xff\x85\x07F\xd7\xf4\ +x\xf6\xe9\x8f1\x05\xec\xe4 \xeb<\xa6\xe0u\x9d\xc5\ +\xd7L\x9a\x10A\xbdLhX\x84OM\xeem\x1f6\ +Y:\xff9\xdb\xdd\xe5xD\xd9\x02W\xd5\x16p\xc3\ +\x13E\x88l\xe7F\x804\x84C\x10\xd9\x16p\x10\xed\ +\x84\xa3\xe7\xe5K\x10x\xc3\xe8\xae\xbc \xc0,6\xfc\ +\xad2\x1fD\xf0x \xf7\xe1\xe1\xd9X\x86\x06Mh\ +\x91\x00U\xc3\xb2z\xc0h~\xbb\xa4\x0b\x84$@\xd6\ +?\x81\xd5eZ\xbc\x8asL-\xa3\x82(\xd6\xd9|\ +\xd79\xd6\xea\xcf\xe5\x1cs{\xbe\x9c\xf2\x9f\xd29\xfe\ +&\x9e\x1c{%\x9e\x92BA\xacH\x91\ +\xca\x83\xa2\xab\x16\xc0eE\xceD\x8d\xae\x98y\xc5\x1c\ +\xca\xd1\xad{\x19\xcb\xf1\x89\xc5\xd7#\xd0\xa1\xc0\x0e_\ +\x0b\xbb\xd2+\x81Q\x9e\xc1\xfc\x8a!\xfa\xf8K\xfcW\ +%\x9d=\x91tC\xc5\x0c\x8c\x13\xad\x80\x11\xbb\xbb\x8d\ +\xa5L\xa8\xa1\xd0:aHI\xd8\xb6\xb9|\xa5\xc3\xf8\ +#\x89)\xf8k\x11\x93\x96\xd5f-Zb\xda\x0e(\ +a\xe8_\x92\x96\xfa\x9b\xd22.\x13\x9e\xe4/IL\ +I_]\xcay\x8b\x9aVb])\xb5\xafK\xcc\xeb\ +)\xdeKpO\xf5\xdd\x0d\xbcJ1\xff\xbc\xcc\x9e\xde\ +\xd5\xd4\xb8I\xeb\x9d\xe1\x85\x0eg\xd7\x81\x86E\x0cZ\ +6\xdc\xe4\x9by\xb6\xa9\x9a\x94\xfbi5\x95\xa6\xbf\xb9\ +-\xe2\x1d\x19\x8eZ\xb7\x93p\xfb\xf6.\x9d\xe7O\x1f\ +.X\xb7\x11\xaf\x97`\xc0\xa6\xa42R\xf4\x9a]\x90\ +\xc8\x13\xad\x89\xe5\xa6\xd7\x88\xeb!\x22\xa1\xa4\xb1Cu\ +\xe3<\x9f=\xe2e\xa6\xf8\xb18\xea\x87}\xaf\xfb\xe3\ +f\x83\x08\xab\xf49\xdb\xb4\xaf\xd64\x97o\x8c\xa9;\ +\x96\xd7s<\xc8\xf6.\x7f*\xe8\x83\x95\x9b\x8fYw\ +\x06l\xf7w\xef\xe1`\xd3\xed\x06\xcf'\xd4\xf1i\xb9\ +\x86\x0equ!\x88\xca\x1eaJ\x8cj\x99\xba\xae/\ +\xeab\x00\x01\x03t-\x1b\x9f\xf1\x15\xa2\xbf\xa2\xc7\xe5\ +<\xdb\x86\xd7\xe4\xda\xe2\x9b\x9b|\x1fn\xcfo\xfe\x0f\ +\x047~Hww0\xc2\x22]m\x0f\xa1\xacs7\ +\x89\x8fR\xb4\xec\xf2U\x06\x227\xcb\x1a\xb7\x14i\xe4\ +\x83M\x8b-\xcb\xa3\x0b\xd2\x1e/\x04\xdd\xae\xf2\x9bt\ +5H\xe3\xfbt\xbf\xbc_~\xc9\xe6\x8d\xd7\xdf\x1e\xc3\ +\xdbq\xa9\xc2\x1a\x92\xc0\xe2*\xf9\xdc=\xe3\x95\xaf\xfd\ +3\xc2Z\xda\x08\x01L\x88\xc6I\xc3\xab_\xcb\xf5\xed\ +\x1e_\xb6\xa3\xa0\xd5\xecT7=\x07\x9a\xb2\xfb\x87\xb2\ +\xb5\x91\xa2(\xfa\xbc\xdc.o\xd0g\xf7v\x08\xb8k\ +\xbc'5\xef@q7%>\xae\x09\xb5\xe46_\xaf\ +\x9eK\xb4J\xa9\xf4u\x89\x83\xdfg\xbbt\x9e\xee\xd2\ +F\xb1T\x10.h\x951\xb9\xde\xcc\x17W\xff\xfc\xf4\ +C\x1d\x8e\xccfW\xff\xce7\xbf7!\x06\x22\xa47\ +\xf9#\xf0l\x1d\x9f\xe1}\xab\xd9\x15\xea\xd9t\xf7q\ +y\x0f\x02\x83\xf7\xf8\xfe{\x7f\xbf\x02\x15W7\xb4\x90\ +\x91\xda\xcd\xa0\xc5\xb0\x9b\xac\xb8\xa7\x17\xbc\xda8\x9f\xdd\ +/\xb1\xd3\xf4_\xbb\xe5j\xf5\x13N\xe2\x85M\xe5\xa0\ +\xcb\xdd*\xfb\xe8\xe6,\x1e\xab]L\xcbmTA\x8f\ +\xb7\xcb\xebiE\x06\xf7\xe9\xb6!\xcf\xadVu\x22{\ +\x17\xaa\xf06\xa0\xd6\xa4\x92LMb\xbcz\x90\xc5\xea\ +\xb2\x22\xe3m\xcdU\x01\x0b\xc4\x85`\x8a\xa1\x01\xa7\x09\ +3`\xb3\xb1X\x8fY\xc9a\xc2\x89k\x96V\x13\xbc\ +\xd2@t\x02\xce&\x07\xbfS\xf2D\x12a\xd9e\x9b\ +5\xb1\xd4\xb7\x09\x937 \x82\xbd`\xb0\xa9\xc4\xb4\x9a\ +\x12\x0a\x91\xe4\xfb\x05P\xf0\x0ah\xfb}\xc7\x10\xbbk\ +V\x97\xae\xd9\x0b\xeb\xb7\xbbM\xfe{v\xb5\xce\xd7\x0d\ ++\x96)D\x98O\x0b\xe5\x07\xdb\xa5\xc6\xeb$\x8f\x1b\ +u\x8c\xf5\x0e\xae,\xd1ks\xaf\xbd-P\x80\xb7J\ +Y\x81\xafc)\x12\x10=\xdd\x1a*DRM`o\ +\x10\x8fL\xbc\xc78\x0c\x06\xa3\x7f\xe9\x07\xdcm\xa2\xe1\ +\xa4B\x83\xec\xd2Vi\x83[\xa2J\x14k\xec\xaa\xbf\ ++\xeen\xa5\xb0V*\xa5\xd2\xfdX7\xa7x\xef%\ +NA;\x1d(\x0f\x1e:\x1d5\xf2t\xceL#\xb4\ +\x09\xde:+\x05S\xa8\xcct3\x1b\xdaD\xaf\xd0\xdd\ +\x98\x13X\x0cg\xd6\xc2\xb4\xf29\xd5\xf4\xeeE\xa2D\ +\xd2\xb7\xd8\xa9iw\x97M\x9c\x226\xa1\xf6\xcd>\xc0\ +\xa8M\xebs\xa0\x15\xb5fD\xa5\xc6\xd0EL\xaa\xc1\ +\xa3Y\x84wh\xac\x90BF\xa0\x0b\xb8\x01\xc50)\ +:Gq\xf1\xb7\xfeX\xe0\x81\xa0\xd7-U\x8f\x06P\ +\xe2\xc21\xd4\xff\xae\xa2\xb2\x91D_B\xcb\xdd\xee\xd2\ +M7\xf1U\xb5ekX7O\xa8\xa0\xd22}>\ +6\xa1\xd4\x82\xefF,x\xcc \x1c\x9aI+\xed\x80\ +l\xbdH!Yr\xb2B\xb2\x01\x85\xe4\x0eJ\x9a\x83\ +\xb2\xeb\xbf\x96\x18\xd4H\x01\xe5pNa\xbb\x9e\xde\x96\ +\x0f\xbe\xcc\xf5g\xa0\x89\xd4\x960\xa5'\x18\xd1X\xc6\ +\x85b8A\xfd\xdc \xc4\xb0\x13\x89\xaf-\xc1\xe0p\ +\x99H\xce\x98\x97\xbe(8\x1a\x0c\x0d\xd7\x10\x13\xd5\xd7\ +G\x1cKS<\x22\x09\xa1e\x14\x9b\xc4\x80M2`\ +\xa9\xaa\xfb!Q\x5c>\xb6@U\x0fd\xef\x1a>\xa9\ +\xba\x9b\x16\xb0\xe9G&M\xcf\xa8\xc2\xf5\x11\x82\xbd\xfc\ +\xc9\xaa\xb5\xd4\x13\xf5\x17\xd7\x88MK\xc6\xfb\x17^Z\ +\x0ab\xa09|\xdf\xa6\xa5\x9eZ\xf7sj}\xe6_\ +\xfa:\xae/\xc1\xe8\x8f\x91\x80\x80V\xae\x99j\xc0\x1d\ +\x81xPIK\x84\xbbG\xa0(\xb8\x18\xcaqj\xfd\ +\xdc \xc4\x94\xe3=\x02\xc5\xc05Q*1\x10*\x8b\ +\x9eCB\x14V*\x8d\xb3\x1cM\x5c\xe1r\xd9\xbe0\ +W-H\x86G\x885b|\x1d&\xc0\x15b\x22\x84\ +\x96o\x96\xb7\xcb5F\x0a?G\x10\xc4\xe3}h\xd0\ +\xa7\xael\x0e\x96\x1b\xfd#\xa2\x10\x90+\x108\xd3\x00\ +\xdduP\x87'Ub\x0d\x07y\xf1`Z4}k\ +\xa0\xc5\xb2\x02c\x19\xf5\xc6\x03!\xae\x06\xac'n`\ +\xd0\xdb\xda\x04\x5c<\xc1<(\xa3<\xe1\x94\xf9\x03\xd6\ + o\xe2\x1a\xe6-\xb0\x1e\xad\xd9G\x7f\xbf_\xa2\x16\ +\x19d\xf9\x80\x8b\x01\x86\x84\xe7\x06\xc6\xd0Aq\x9d\xa4\ +I\x84T\x1c$\xa5\x81\xb5\x16S\x01-I\x0c*\x14\ +\xd3\x0c\x07Q|k\xfb\xc2\x91\x5c\x82\xc9\xe1\xa6h\x94\ +\xcc\xef\x07\x9f|\x02\xc3\xc7f\xf6\xba_\xbd\xc6\xc0V\ +\xbeD\xe3}\x0e\xbc\xbc3$B\xef\x8b\x9a\x99\xa2k\ +s\x87\xe22\xe4\x8a`\x01W\xd7\x1d\xa8V7\x11`\ +cA\x1b\x80\xee\x8c\xf1\xee\xac\xb1\x85\x99\x07\xa9\x11\x0a\ +\xe0\x13*\x12\xa2-'\xca\x83U\x0f\xa8n\xcb>@\ +\xad\x02\x916\xad}|o\x06\x84\xe1\xfc\xb1\xebg\x14\ +\xa8\xbf\x1a\xe4\x8d\xef\x16e\x09Q\xa8\xa8\x01\xd3\x15i\ +5\x08q\xbf\xcf\x97\xe8\x1e\xd7\x95\xe01\xcd\xe0`\x12\ +\xbc\xc9\x05\x9a\x22bx\xa1\x10q\x91C\x15%\xc8\xd3\ +\x15\xa8z\xc0\x9eE\x0fh\x8d\x1d\x22\xe1E3\xce\xd6\ +\xc2G\x80C\xe6\x8eb\x05h\xe2:Y\xaa\x08m@\ +%6\xee\xc6\xe1s\x0bB\x81h\xee\xe6w=\xb7\x8f\ +^0\xcc\x11\x83\x0d\xd3P\x8dQW\x91\x16\xac?R\ +\x89%\x96\x92\xd1\x89J\x984\xc4\xdd*?\xe8\x1a\x87\ +F6TS\xa6\xa9-G\xae?r\xe0\x16\xd8\x1a\x9c\ +\x01\xac\x1f\xc2Hk\xf5\xe5\x00oA\x88iy\xcd\x5c\ +\x16\xdc\x1c\xed\x98\x8b%(E\xc8[\x12v\xccE\x03\ +*\xff\x16\x9c\x85\x1d\x94\x8a*\xb4\xba\xb1\x8b\xec\x8d\x8d\ + T?\x8e\xaf\xa4\x01K\xc4=X3z\xb9\x1e0\ +\x07\x8e\xaf\xf0\xde\xa1i\xda\xe3~\x97\x82\xad8\x90\xa2\ +\xe0+\xb0\xa1\xd6P\xc7Wx\xcf\x19\xfcjP\x14\x10\ +\xec\x98\xf2\xb3\xfb/n\xa3DD\xfeE\x14p8]\ +\x9b\x1b'np\x8b\xcf\x0e[\x0b\xea\x18\xaa\x84\xba\xca\ +0c\xb8\x01\x9d\xe5\x01\xebN\xb8\x99\xb2\x1b\x8f\x0aT\ +\xca\x8av\x19\xb5\xf1\xdb,\xd5\xc4*$\xf0\xee\xb9q\ +y\xa5 \xa0\xc9\x15}\x7fPY\x092h\xefC\xca\ +J\xd0\xb6\xb2z\xb3\x8aoV1\xe0C\xe1\x9dd0\ +*\x96\x04\xa3\xe1\xb0KvP\xd3\x1dSO\x9a\xf5\xb4\ +\x93\xe4\x07u\x93fQ\x892\xac\x98\xdc\xa8\x1d\xbd$\ +\xad\x12\x03zII\xaeJ\xbd$\x88\xcfP\ +\xc7\x81\xe6\x9am\xb7\xdbf\x09\xcd{Z\xdd\xbc\xbcu\ +|$q\xdd\xd2\xc2\xb9P\x86R\x05\x8bp\x8c\xc4A\ +\xf1I|w\x817\x93\xf1\x8b\x94\xc1\xe9\xd0x1Y\ +BDH\xf1\xd5\xa5\xa4R\x15@+1N\xa5\xa8\xaf\ +\x8cf\x10Xb\xc8\xc3\xb8\x9d\xa8DP-\x84F\xff\ +M\x80<\xc0,\x16B1x\x8c\x98\x04v\xc0\xea\x0d\ +\x80 sB\x9c\x86\xdf.\x0aS\xe1\xfb\x7f\x98\x07\x5c\ +Bf\xe0\x1c\x89A*\xd3\xc40\xae\x15G\x98b\xc4\ +h\x80a%\x19\x84\x7f\x1cdY\xc33\x9c8,\x97\ +\x09\xbcH\x8dN\x9d\x02\xd7S\xe2\xeb}\xd0O\x04\x1c\ +8\x15\xdc\xab\xf7\x0a}\xd8\xdd\xc1;\xe0\xa3\x22-\xaf\ +n|\xbd\xcef\xbb|\x13\xcf\x1e7\x9f\xd3\xdd\xe3&\ +\xc3j\x96\xfa,\x8f\x96C\x94\x89\x00\xe9\x8a\x1c\xac\xb5\ +\x04\xc3eW\xa0\xa35a\x9d\x82\x07\x8dE\xdb\x94\x03\ +J}\xff\xd4+\xe2\xf1\xaax\x8e\x95\xf1\xc8@\x1dO\ +\xa8\x90\xa7\xc6\x8f\xea\x81\xab\xc6\x0e\xae7p\xd9R\x0f\ +\xdb^\xc2\x81z\x86N\xcd\xd2P\xb5S\xab\x92\xa1\x7f\ +\x11\xb7U\xc9\xd0\xbf\xbd\xdb(\x5cJ\xba|\xe1\x05\xe5\ +L\x00[\xd2\x81\x17\x1e\x9c\x8e+\x08\x1b*p\x08\x0a\ +x\xbf<\xed%\xabc\xe3R\x06\x0d5\x18\x19K\xc1\ +\xb1\xf4\x1f:\xba\xc0\x91\xff\x059\xbe\xab0ry5\xba\x19_\xbe~}\xda{\ +\xbf\xd9\x5c\x8e\x86\xc3\xe5U=\x9e\xae\xde6\xf5du\ +1\xc4\xa3!$\x0e/.\x86\xbd\xbb\xde\x8f\xf7\xddu\ +\xbc\x9c\xce\xda;\xe2\x01:\xda;\x89\xa7=y8\xc2\ +\xf5E\xb3\x19\xef\x1fL'{\x01\x97\xd7\xebE\xbdZ\ +\xbf\x1bN'\xc3f\xd1\x5c4\xcb\xcd\x15\x84\xe8}\xdf\ +\xc9]\xdf\xc9\xba\x19o\xe6\x1f\x1b\xccu\xb1\xc2\xcc\x1c\ +\xb6\xbcz\xbe\xeb\xb9>X\xd7\xa7O\x9f\xeaOVz\ +\xe8\x9c\xf3P\x99\xa11\x03\xf4\x18\x5c\xdd.7\xe3\x9b\ +\xc1\xc18\x00\xd66\xce(\xa5\x86x\xb6\xed\xf6\x84.\ +\xa3\x9b\xc5|\xf9\xa1s\x0d\xf2\xb4W}\x9aO7\xef\ +O{.]nz\xd5\xfbf\xfe\xee\xfdf\xd7\xfa8\ +o>\xfd\xb8\x02l\xaaR\x95K\xf8\xe3\x09\xdf.p\ +\xb8+(\xc2l\xb1\xfa4\xfa8\xbf\x9a\xbf]4\x7f\ +j\x96c|\x0d\xde\x8e'\x1f\xde\xadW\xd7\xcb\xe9h\ +\xd9|\xaa\x0e\x06\xce\xa7\xa7=\xac\xcd\xf3\xfa@\x8bt\ +\xef\xc5\x09\x17\xb2\xdbU\xaf\xda\xdc^b\x82Ms\xb3\ +\x19N\xae\xaez\xd5\xbaY`$\xa7\xbdz\xdf4\x9b\ +\x22i:^\x7f\x18\x5c\xac\xa6\xcd`r}\xb5Y]\ +\x0cd3\xc3/\x94\xf5\xaeY6\xeb\xf1b/Lz\ +\xdeI[\x8c\xa9\xea\xcd\xf2\xa1\xdc\xd6%\xc9\xe8/\x10\ +\xb3\x14\xed\xda\x8byVU'\xd3fvUz\xe1\x22\ +\x04\xd7\xe3]\xdc_\x8f\xa7\xf3\xf1\xe2\x9f\xf9\x05\x8d\xad\ +\xdem/~]\xce7\x98\xf2\xfa\xaaY\xffr9\x9e\ +4\xff\xb6\xfc\xf5\xaa\xe9\xed\x1f\xbfY\x8f\x97W\xb3\xd5\ +\xfa\xe2\xb4w1\xde\xac\xe77\xdf\xaa\xdaF\x97\xfb\x8a\ +\xff\xe1\xd2\xf7\xb5\xabs\xf6\xb9\xaf\xfdw\xbdjv\x0b\ +\xcd\x88\xb5\xca\x1ag8\x83^\xb8\x5cg\xe5,p=\ +\xedYU\xabdC\xaf\x9a\x1c\xf6\x9a\x1c\xf6\xe2\xc2\xff\ +\xe3\xf5\xab\x9f\xce\xff\xa6\xfd\xdf\xb6K\xc7\xe2\x01\xd6e\ +Q\x0f\x5cx\xef\xf7J\xc6\xf6`\xb2Z\xac\xd6\xa3\xe7\ +\xc9\x9f\xfd\xf0\xf2e\xafZ\xcdfW\x0d4T\x15D\ +\xda\x04\x84V\x01J\xd9\xf3\x1f\xf2\x9d\x00\xbd\x13p2\ +O\xc2\xb5\xfd`\xbc|\xfe!p5\x88\ +5N\xdb\x82\xab\x89\xb57z\x87\xab7\xe8\x99\xb6\xd6\ +;p\xb5\x11\xa3y:\xae\xb9\x13\xd7\xd4\xee\x8b^\xca\ +\xe7)\xb8\xa6\xd8* \xe7\xb3\xb3\xcf\xb1\xde?.>\ +\xd8\x88\x1bA\x97\xf8\xa0Cm\xbd\x0e\x12\x1f\x10\x05\x9c\ +f\xf0\x9f\x1c\xf6\x9a\x1c\xf6\xba\x83\xd4\xa8NH\xb3\xfb\ +\xc2\xf8\x90\xdb\x03\xcc\xe7\xc6\x87\xdf\x13R\xed\xa8\xc3\x88\ +\x19\xa6/_Z\xd0\xd5\x80\xd7\xd6\x088\xa1\xaf\xc5\xf2\ +\xed\x16c\x9dj\xc5\xb8@\x88m\xaauH\x82p\x86\ +\x19\x04\xfa\xe3\xc9A\x9f\xc9A\x9f\x03|u\x17\xbeA\ +\xb5\x07\x9c\xfb\x1a\xd7\x89oPO\x0b8\xaa\xf6\xdd\x22\ +\xda\xcf\xd8\xda\x10:\xb4\xfe)!\xfcQo\x92U\xe4\ +\x19h\x1b\x5c\x7f\xffU\xee:\x040\xeb\xfa\xbe\xf6!\ +\xc4\xad;\xd1\x1a\x01\x9f\xc6Io\x92\xeb\x10L(\xce\ +\x84\x06a\xb2)\xbed\xf7\xe0\xc9\xae\xc4\x98\xces\xd1\ +_H\x04\x82\xee8\x97\x1f\xf3\xf9\xf1\xb9(\xefB\xb7\ +\x94\xf6\xa3\x09\xeeG},\xc5:\x1b\xbb\xa5t\x90<\ +}\xa6\x8e\xa5\x04\x15\xbb\xd5D\xb7{\xd7l\xcf\xeei\ +Z\xb2\x8f\x08\xf9}<\xecW\xd45\xeb\x8a)\xdf\x14\ +\xce\x85\xe3\xd9\xb2Nx\x87h}*\xba\x86\x86\x93\xd3\ +y\xba\xb2\xd9Ne3\xed\xba\xf2\xe4\xb8\x15L\x87\x9a\ +\x84\x1f\xed\x17\xb2N\xe7jg\xdd\x16\x0e\x04\x15\x13\xf7\ +p0\xcf\x08\xb1\xc0\x01\x0e\x1a]\xf8,\xdbs\x9dp\ +\xd8v\xdb{:\x1c\xb6\x1d\xcf/T\xb2\xdb\x92\x86\x84\ +`\xd3\x16\x0e\x0bJ\xb8c\xe1\xf0D)\xee\xd8\x22c\ +l\xd4\x9f\x93\x93\x98\xce\x14-\xd8/d\xe1\xc1u\xb1\ +\xf0\x10\xce\xce\xbe,'\x09\xc8\xbd\xa8\x107%_\xf3\ +\x8a\xd4V\x92\x92\xfd\x13\xc2aj\xa7\xec\xe7\x90<\xd3\ +\x99\x94\x04\xd7\xeeA\x9eL\x9e\x83kg\xdf_H\x9e\ +oKV\x16\x5c\x8e\xfb\x14M;xB\x81\x03\xd8\x04\ +\x13\xb7\x9c\xd7\xe9\xda;\x97?\x07\x8e\xce\x5c\x22t$\ +\xf0g\xfe\xa5z\x9avt$\xf0\xfa<\xba\xf3v\x82\ +\xf6\x10\x8e\x93!+$r\xc5R\xdft\xbc\x19\xcb\x04\ +\xbb\x86\xc7n\xb7\xa3o\xb0\xc1\x0f\x0dF\xbdm\xde\xcd\ +\x97\xa7\xdf\xfc\xf7\x7f\xfe\xd77\xec\xfb\xcd_\xfck\xf5\ +\xfa\xf2\xacy?\xff\x97\xdf\xd6\xcd/\xbf\xfd\xeb\x9b\xc9\ +o\x1f&y\xfa\xcd\xc9p?\xa6H\x18\xdd\x5c\x5cR\ +r%W\x9b\x0fD\xe9\xe7j\xb3Z->\xcc7\x95\ +\xad\xd5\xc0\xe4~5[\x8f/\x9aO\xab\xf5\x87J\xd7\ +\xe1\x0e\xbd\xdd\x92v7\xc8-\xa7\xb3\xd1\xbf\x9f\xbf\xbc\ +\xbb\xb3\xbdw\xde\x5cM\xd6\xf3\xcb\xcd|\xb5\xac\xd8\x1e\ +\xbf]]\x03\x89\xeb\xeb\xf9t\xa4\xa7\xde\x197\x9e\x0d\ +\xdeNfa\xe0&6\x0f\x92J\xb3\x81\x091\x1b\x03\ +\xcd\x9fNL\xefP\x22d^B\xc6\xcf\xeb\xd5\xf4z\ +\xd2\xac_<\xfb\x81\xc5\xd1\xea\xe7\xf3\x97\xd5b\xfev\ +=^\xdfV\xbeV\xeadx\xdc\xeb`\xf8\xf0\xde\x9a\ +~\xff\xf5\x0e\xff_\x88\xbc\x87\xea\xcd\xf8rt\xc6\xda\ +ps\x8e\xff_<3H-\x06\xf8\xd3\xea\x8d2#\ +\xfc\xe9\xf0=/\x00\xed\xfd\xae\x0f\xe4\xbc^M\xe7\xb3\ +\xdb\xbd\x1cG9&\xbfQi\xe4\xf3\xc8\xc5\xbf\x16\x11\ +\x87\xbd\xda\x97\xb2Z\xbf\x816\xee\xce\xf8\xa7\xc5\xe2\xfa\ +j\xb3\xe6\xedJ\x83\xf0\x1d,d\xd7\xf1\xe1J\xb6j\ +z\xb8\x16=\xd0\xe9\x8dN#\xe7F\xda|\xaf\xf4~\ +O\xc7\x9d\xffP\x95y\xb8\xf4\xd7\xafG\xe7\xab\xc95\ +k\xf9?\x9d\xbfx&\xa2&j2V\xd3\x94\x06S\ +;U\x037m\xdc \xa5\xd9\xdb\x81\x8fa\x12\xa6!\ ++\xedg\xb2\x95{\x83\xff/\xb72\x9d\x8c\xc8$\xc7\ +p;\xf3\x8b\xf1\xbb\x86\xa5\xfe\xefo.\x16\xf0uw\ +O\xee\x8f\xd8\xcc7\x8b{Z\xb1]\xde\x0f\x8b\xcd\xfd\ +\xfb\xdb'\x8b9\x8b\xd5\xa3R\xa7\xbe\x19\xc0\x93\x8e\xaf\ +\x17\x1b,g\xb6ZL\x9b\xf5@\xd7\xe3y\xd9\xfeb\ +\xfe@\xf4\xb0U\xb6\xac\xf1\xc1Z\x1e\xc7p2\x19\xfd\ +\x85\xde\xf2\x00\xbbnH>\x03\x91\xdb\xcbFd\xae\x9b\ +\xab\xd5\xf5z\xd2\xb4\xbe\xfc\x99N.\xe6\xec9\xfce\ +3_,~\xa2\xf4c\xd7q\x00\xef\xb1K\x19n\xd7\ +}\xe0\xcf\x87\xf7\x1c\xfa\xc9\xf0\xd8\xe9C\xd3v!\xe4\ +~Pj\x96\x88D\x9f\x8eC\xce\xf1\xf0\x93\xcb\xf1\xe6\ +\xbd\x047^x\x16\x9eq\xfd\xbar\x11\x09\x81\xef\x93\ +\x08'[\x9d\xb1m\xd9\xf2\x99\x97\xca%6\x5c6\x95\ +\x033\xca\xd2\xd1\x9bP\xbd\xaa@\x0d\xac\xc5S_\xdb\ +\x14+\x9b\xea\x94\xa4\xd0\x03)\x93\x8au\x0a+\xe5\x0a\ +\xe3\x22[\x1as\xe0\xcb\x85\xcc\x96\x0d\x96\xad\xa0\xa5\xa5\ +s\x92D\xc7\xb3\xe1\x9cd=F\x07\xb6B\x96\x8e:\ +\x99J\xf5U50`d.\xf4-\xdf\x1b\xa0\xe5 \ +7\xa3eM\x92)\x95T\xf9Tp\xd22\xa5\xe6\xa7\ +\xf5\xc3\x96\xf1\xa6\x0f\x05M\xd6V\xfc\x8aX\xaa\xad5\ +\xa5\xb0\x0e\x8d\xa5\x1al'W\x8bj\x80\x1c+\xb1\xd0\ +\x12\xb1r\xd9W\xd2,(jm8\x12I\x16\x1a0\ +R,\x06`\xe5\xbe\xc6\x0c\xa6\xec\x83\xe9\x9aOe\x1b\ +\xc9\xe2\x895z\xd7@g\xc8cyL\x03h\xac\xae\ +\x14st\xe2\xb5e}\xc7\xc5\x88\xe9\xb1(\xaf\xfb\xd8\ +u\x8a\x06\xd3\xb3c.\xc9 \xafC`i3x\x5c\ +k'\xc5\xa0\x0c\xd2\xa0\x95\xc3d\x11\xd8\x02,c\xfa\ +,\x0e\xe9\x8ae\xce\xd8\xc7\xe1\xe1|0Hy\x8aI\ +\xba\xe2LY\xc48\x5cK\xd9\xae6F\xe6\xe6\x8e<\ +\x93M\xa7\xadLn\x00}9\xd9J\x8eR\x8eF\xc6\ +\x05-Gh\x83\x915\xcaYc\xa9l\x18[\xc6H\ +\xc3\x16\x01\xb2g\x9dd\x8cJl\xec\x94\x85\xd7N\x19\ +\xd1\x15\xcfU@\xa1(\x1b\x99\x09\x92\xb6\x02A\xf4\xa5\ +o\xd8\xe2\x81\x86\x17duY\x1f\x08|\xf5\xdb\xdd;\ +HX\x04lt\x84\x11\xdb\xd7c\xe0\xab\xfbW\x98Z\ +\xa9\x7f\xda\xbf\xdd,\x8d\xcd]2.\x97\x0bD\xa7o\ +\xcb\xe2d\x8f\x91EL\x1a\x12\xe4x\xcf\x14\x93/\x02\ +G\xef\xd7\xcd\xec\xb4\xf7|o]\xb7$\xaf\xd5\xcd\x9e\ +\xc2\xfe\xbd\xd3\x22I\xee\x8b\xda\x99\xef\xb6\xe2v\xa2[\ +\xa7\xdd/\xccn\xa7=\xb6\xfb\xa0vv\xefa\xce\xb1\ +o,\xad\xb82\x1a\x87\x0c\xe3\x0e\xb4{Qs\x9b\xad\ +T\x19E\x99C\xa4\xa6\x89U!\x0d\xa15\xe4\x80\xb3\ +Q5,WC\xb3T\xd1\x0f\xda\x18\x8fO\x0e4Q\ +\x8b#\xf5\x01\xb1\xaaOS\xe6\x10\x9d\xa0\x81b{*\ +%\x19d\xb7eM\xf6\xf4\xa9\xa8\x81\xa5^\x86r\xce\ +\x1e6z\xac\x06i\xab\x06\xb6\xa8\x81;P\x03\x95\x8a\ +\x1e\xc4\xa2\x07\xa6\xb8\x1e\xd1\x0a\x9d\xad\xd8e\xd1T\x93\ +r\xd9\x984|8P\x98\x19<\xf9\x08>\xfe\xdb\xe7\ +\xfb\xb7\x90\xdf\xb5c\x19\x05\xcb\x0b\xa2\xe7\x89\x1e\x5c\xa7\ +\x98\xc6\x1d*\xc7\xdb\xe5&\xf2^\x97\x1f\x991t\xcc\ +\xc8<}7\xa3\xd6\x813fG\x9f\x91\xbdT\x81\x91\ +\x9eqJ\xc3ma\xca\x88\x19C\xe6\xb5g\xfa\xdfj\ +\x14\xfeO\x0ff\x8f]\xb3\xfb\xdd\xec\x16gJ\x8f\x96\ +\xe1\x85'\xf4\x83F\xd9b\xff\x96\x8e\x10\xbe\x8b\xdeH\ +\x9cb\xa23\xf2\x86g8\x88\xf0/\x121B\xdc\x06\ +\x09\x06\x02\x19ME0}\x0e\xa5\x0bC\xff>\x87\x1a\ +\x8c\x82\xf3\x83\x17\xed\x07b\x87Ae\xae2\x15\xbb\x17\ +\xbfc*v\xd7\xdc5\xe7z\xc5\xd8\x95\xe1F4r\ +(\xf8\x06\x845\xa2\x02\x8c\xe8\x98\x13\x9f\xf2Xr\xad\ +\xb1\x10\x046\xc41\xc4/]\xfd\xf5\xf7\xf7\x1a\xb6h\ +1\x1c\xeb\x81\xd7\x88\xa1\xcd|\x89\xef\x03\xafq|\x04\ +\xc9\xde\x1d\x81\x92\x97Y)\x84\x7f\xdc#\xd09\x13r\ +\x12\x06a\x16>h\x80n\x8d\x80\x9e\x10w\x12\x19\x06\ +}\x11=LDh\xf4\x07g\xf0@1S\x87b\xa6\ +\x9dY\xc0\x04\xb4\xc3\xba\x93\x98\xf4\xa4Dz\xba\xae\xe4\ +\xe9^\x8c\xf1\x8c\x8c\x8e\xfbc\x14A\x80Sa\xb7\xbf\ +\xc8\xfd\x95 \x9b<\xa31Eh\x81k C+v\ +\xf7\xc5\xb0\x84\x00\xc1Y\xfa\xc8\xfdY+\xcc\x89\xf88\ +\xec\xcf%aK|\xbbH\xf3w\xdc\x9fC\xd0O\xc2\ +S^q\xba\x94\x03\xcf\x04\xee\xea\x8cM\xc6Y\xa0\x0a\ +M\x95\xf7\x8f\x89\xab\x84L\x83\xa5d\x84\x7f\x09\xda\xf7\ +py\x9e\xf3d2\x9b\xb5\xe2\x91w\xe4\x0e\xa2\xe1\x04\ +!@c\xcf2\x93Q\xf0\xf9\x9c%$\xde7I\xa6\ +\xf4r\xf0\x11\x94\xe3\x151\x8c`r\xc0\xd0\xf9\x02\xa1\ +&WpYh\x06:b\xffF\xfc\xb3\xc1\xe6aA\ +G\x0aR\x00Dg\xd2\x0b\x1c-;\x97*\xb6!5\ +)\x9c!\x08\x0c@%\xc3\x87i>\xcc\x05?07\ +\xe0\x17\x9c\x95\xf0\x04\xc8`\xa1Z\xf4\xc3$\x9ah\x06\ +izD?r\x87~\xf0E\x8b\xe8\x87\xf5\x0c\x03|\ +O\x96\x0c\xc9\x84e\xc9\x5c4\x16\xb3\x04x\x08iD\ +(K\xc6-\xc7\x86\x0d\xdc 8\x22N>\x80\x22\xc6\ +\xaf\x7f\xd2\x85u\xa2\x1f\xc4\x88\x83\xc5\xca\x10\xb5X\x8c\ +\xd4<\x1e\x1fM\xa1\xb2\x95\xce\xd4l\xe1\xae.\xb6\xba\ +wW\xdc\xfb\xf3\x99|\xda\xd1\xc9;tx~0X\ +\xb1c\x9e\xe3=\xc6m\xb6\x8c;\x09\xe3\xe6\x91\xa1k\ +\xc0\xfc\x08h\x1a\x86\xc4\x16\xf5\x03\xe7\x0a\x16\xf1\xa0\x05\ +'\x8b\xf4@\xc3\x17\xe0\xc8\xa3O2\x91\x05\xd2JK\ +\xbcT\xd4>[;\x1b\xd9J\xa2\x13\x90\x01?\xc4`\ +\xeay\x06\xc4\xd2A\x10\xae-\x14\x02\xb2\xd5VG\xef\ +\xd1m't{w\xfd8\xdb\xb6ml[\x1f\xb1m\ +\xdf\xca\xb6\x93\x90mq)\xb0\x02\x92mK\xb2\x8d)\ +\xee\xc8\xb69 \xdbF\xc8v\xea&\xdb\xae\x90mw\ +L\xb6\xedC\xb2]\x88h\xa0\x15\x16\xae\x8d\x14\xaa\x92\ +\xa4I\x88\xb3P\x02\x9dDKBv\x845\x96\xf4\xcc\ +\x8aqm\xf33{\x94\x9f\xb9\xa3\xfcL\x17;-\xe9\ +Y>L\xcf\xf2Qz\xe6w\xe9Y7\x9b1\xaa\xdd\ +,\x83R;\xc5CL(\xc9c\x14\x8e\xdf\x92=\x1e\ +\xaf\xce\x1c\xae\xae-y\xf4\xf7\x92G\x98\x94\x11,\x18\ +\xd3<\xb3<\xce\x07\x97\xb7\xa3\xbb_\xc5\xc7aP\x9d\ +\x09s\x86\x86\xf8V\xfb\xd4\xad\xc8\xe8\x1dK\x87If\ +/\xf6\x00\x05\x05\x99I\xf2\x92\xdf\xd0\x86\xa0u\x88\xc2\ +\xe4\x92\x89\xd1U3\xec\xea\x82\xa1\xa5\x85\xc9\xef.\xb0\ +O\xac\x03^\xd4I#\xe7\xd4\x92\xd3\xc2\x1fIJK\ +t\xa1\x86x\xa4%\x95df\x9c\xe9wH\xa4\x8bu\ +\xdc\x19\x87\xde\xba\xb2\x80\xb5\xc0\xb6aQx\x0e\xd5\xc5\ +J}\x9f6\x9aJ\x8d\x00\x9a\xc4J\x81~,\xb0\x1b\ +\xdd\xa1!f\x17\xc8\xa8\xb7\x9a\xf6n\x8d\x13\xb7\x10\xc5\ +\xb38\xfaQ\xfe\xa0\xb0/o\xae\x98\xf1\x1a#\xfe\xdd\ +f#\x09\x8a5\x0c\xf3\xd6\x97\xd4\x11\x81\x86G\x8bt\ +\x16\xff\x92\x1e\xc2\xf9\x821\xd7\xd9\xd0a\x04\xc5s.\ +\xe0\x0cv\xe8\xf0\xc7|\xe5t\x09:\xdcr\x12\x06\xe0\ +9Lr\x09#\x19\xc9B\x14\x93\xa9\xdan6\xc9C\ +\xa4\xaf\x96\x22\x83\x22?+\x13\x0e\xca\x8c\x03N\x99\x1e\ +\xb3\x1c\xd3\x85K\xda\xd1@@\x00?\x98%\xbd0\xa2\ ++L\xe5J\x86\xa4%\xef\x22\x8da:G\xf7\xa8x\ +\xe9%\xb3\x8b\xc2\x88\xb0\x0fF\x8f\xac\xa9\xb9\x81{c\ +F\xa1\x99\xa9\x90\xd616{\xe6- \xfa\xec\x17\x94\ +(\x1e\x9c\xbd\xc4\x1cdw\xc0=\x1b)\x12\xc0\xbf\xa0\ +c\x8c\xc2\x908S\x02\xdc\xd8\xb2\xc7\x13\x83FrV\ +\x042\xc2\xd1\xc0\x8b\xddFU\x0c\x5cf\xabM>n\ +\xb4YLz\x90\xb0\x18\xdb\x05S\xde\xaa\x0f\x0c`\x1b\ +:\x02\x9d3+<\xd0\x1emS\x091\xa4\x04^\x5c\ +;\x0e\x93\xe6\x16$[\x04mB\xf2D{S\xe4{\ +8X6r\xa1R5\xa9\x0b6o\xa9\xfe\xcc\xa4i\ +\x898M\xe8\xbf\x96\xc8\x94@\xadZ\xb5I\x17m\x8a\ +$\x97\x89>\xc4\xe7c\x1d|%\x18\x97\xd0\x19\xc5\xb6\ +\x99R\x8am\xe7b\xdbZK,5\xce\x1co\xed>\ +G\xec\x8e\xfa\xc1\xaa\xbd\x0aYn%q\x01\xe2\xaf2\ +Uhg1$\x1e\xf87Gz<\xe7\xb71\x96a\ +\x95\xac\x1a\x9e\x80!\xdb\x98\x5c\xd4\x8e\x8b%\xa7\x82\xd3\ +\xa0\xcd\x97@h\xa8\x9f\x06X{:\x08Q\xd6H\x92\ +c\xb9IM\xd51)\x14\x85\xd5\xb1\x14\x8e8\x83)\ +\xf1I\xea\x02\xb6D\x1dV\x94\xc4\xd0@\xc0%\xdd\xf7\ +\xf6\xbe\xf5<\xba\xe5\xb8\xf3&q\xc7?\x22]\x01Z\ +\xe2L\xa2\xff\xa3\xc8/\x09%\xd3S0J\xab\xda\xf3\ +r\xfbP\xcd]\x87\x9a\xbb]\xfa\xe3\x88L\x9f?\xb8\ +U\xe2\xbd9E\x99a\xeb\x8e\xa4<\x93K\xe8\x8e\xe4\ +0\x11\xfa5(K\xdb\xd1r-\xf5>J\x09f\xcb\ +\xad\xb6Q\xde\x89u\x22K\x90m\xc4\x12c\x8d\x94\x1a\ +t\xa1OY<\x1e\x94(J\xadH~z\xa9AZ\ +\x19o\x09\x18l-}\xdd\xb4\x8c\xc8\xb2\xa0\x09\xad\xc2\ +\xee\xdb\x91\x8d\xf23^\xf3\x10\xe0\x8eBO\xf0\xe6\x0e\ +`\xfe\x84\x94\x9a\xab\xc4\xa9s\xaa2\xd3vWq\xbb\ ++b\xe2\x85\x85\x96\x1c\x5c\x09O,+\x1cl76\ +\xd8\xee\x8c\xd5\xbdR\xd2\x84\xd1\x0ddH\xbf\x8c\x97\xb8\ +\x11\xa5\x1c\xad\xc4\x07\xd1\xe3\x84,X\xc2\xb3\xc4\xe0\x91\ +\xfd\xe3\x9ea\x5c\x93$\xba\x92\xdf\xfbjj\xb0\xda\x9d\ +\xdfW\xca\xf0\x095\xbd\xe5\x80?'y\xb2\x12\x1f\x14\ +\x97V\x8b\xdbw\xabe\x81\xb9\x5c\x07o{\xd5\xe5j\ +\xbe\xe4\xcf\x0a\x1cy\x89!{\x80\xbb\x00EL\xf0\x0e\ +V8\x1e\xdf6DV\xb6\x09\x8b\xa6#\xa1\x1a::\ +o[\xdd\xf3\x07\x08\xbb\xd3q~dJVDwS\ +\x92\x88\x82xB\xaeC\x98\xb6\xf0l6\xf4\x1d\xcf\xc0\ +\xc9\xfb\x0e\xe4\x1c\xaeT\xac\xbbcvG\x91\x22\x04}\ +\xc7v5\x18\xbcEz`K\xf0\xd3z[\xe1\xad\x06\ +\xfcU2\xcf\xa1\x10af`\xd8q\xa9\x93\x07z\xc5\ +\xe8\x8bGQN\x18X\x96\xc3@\xdcU\xb4QO\x13\ +9nt\xbb\xc7\x13\xbe\x06{\xf1?\xc6\xc8&\x02\xb0\ +5\x00\x00\ +\x00\x001:\ +\x1f\ +\x8b\x08\x00\x00\x00\x00\x00\x00\x00\xec}ms\x1bG\x92\ +\xe6\xe7\x9d_\xc1\xd3|\x19E\x00\xc5|\x7f\xd1\x8cw\ +c\xd6\xde\x99\xd8\x08O\xec\xc6\xcd\xee\xde\xde'\x07\x04\ +\x822ohRGR~\x99_\x7f\xdd\x00\xc9j\x08\ +j4\x80\x06d\xc9g\x94)\x03\xd5\xd9Y\x95Oe\ +eeeWU\xff\xe1\x9f~\xfc\xee\xfa\xec\xfb\xc5\xdd\ +\xfd\xd5\xed\xcd\x17/\xb0\xc0\x8b\xb3\xc5\xcd\xfc\xf6\xe2\xea\ +\xe6\xcd\x17/\xfe\xf3?\xfe4\x8d\x17g\xf7\x0f\xb3\x9b\ +\x8b\xd9\xf5\xed\xcd\xe2\x8b\x177\xb7/\xfe\xe9\x1f\x7f\xf3\ +\x87\xff1\x9d\x9e\xfdyq\xb3\xb8\x9b=\xdc\xde\xbd:\ +\xfb\xe3\xc5\xed\xeb\xc5\xd9\xbf^_\xbf\xbb\x7fXf\x9d\ +!\x15(09\xfb\xeb\x7f\xfd\xf9\xec_~|{{\ +\xf7p\xf6\xef\xd7\xef\xdeL\xff\xf5\xe6\xac,3\xffk\ +U\xe6\xab3+\x00g\xff\xfc\xee\xea\xfa\xe2LQ$\ +^\x9e\x9dM\xa7M\x11\xf7\xdf\xbf\xf9\xcd\xd9\xd9YS\ +\xbf\x9b\xfbW7\xf7_\xbc\xf8\xf6\xe1\xe1\xed\xab\xf3\xf3\ +\x9b\xfb2k\x8b+\xf3\xdb\xef\xce\xff:\xfb~\xf1\xa7\ +\xdb\xbb\xff\xb5x}\x8e\x05\xce_\xd4;f\x1f\xbea\ +Y\xd3\xa6\xfc\xff\xbaZ\xfc\xb0\xb8\xfb\x97\x1f\x1f\x167\ +m5\xee\xcfy\xfd\xf6\xab-\xb7w\xe4\ +\xbf[\xcc\x1e\xae\xbe_4\xc5|\xd7T\xa8\xbd\xb3)\ +\xfb\xb7\x1d\xe2\xbb\x8b\xcbg\xea\x1f~\xf8\xa1\xfc\xc0K\ +\x22\xcc\xccs\xa0s\xa2iC1\xbd\xff\xe9\xe6a\xf6\ +\xe3t\xfd\xd6\x06\xcc\x0f\xddJ\x00p\xde\x5c\xab\x94\xbb\ +Q\xbd\xfa\xf1\xfa\xea\xe6o\xbd\x95Y^\xed\x96\xde\xe8\ +\xd3\xdb\xe6\xef\xf9\x86\xa7\x8cr\x7f\xfb\xeen\xbe\xb8l\ +\xee\x5c\x94\x9b\xc5\xc3\xf9W\xff\xf1\xd5\xf3\xc5)\x94\x8b\ +\x87\x8b\x0e\x9b\x86\xe9\xfd|\xf6v\xb1V\xeeS\xe6\x0a\ +\xaf\xd9w\x8b\xfb\xb7\xb3\xf9\xe2\xfe\xfc)\xbf\xbd\xbf\xab\ +\xe0\xb8\xcc\xb8\xba\xf8\xe2\xc5\xd7\xb3\x9f\x16w\xdf\xac~\ +\xffpu\xf1\xf0ms\x99b\xf9\xf3\xdb\xc5\xd5\x9bo\ +\x1f\xea\xef\xef\x1bm\xf9\xe7\xdb\x1f\xbfx\x01gp\x86\ +\x14gO\x17n\x1b\xce\x97\xd7\xb7?|\xf1\xe2\xfb\xab\ +\xfb\xab\xd7\xd7\xab\xe2\x167\xb3\xe6\xeb\xf4\xf5l\xfe\xb7\ +7w\xb7\xefn\x9a\xc2n\x16?\x9cun~\x92\xea\ +U[\xdbF\xa0\xb7w\x8b\xfb\xc5\xdd\xf7\xab\xdb\x9f\x00\ +x\xf5\x5cm(L\xcbKORu/\x89\xad\xdfu\ +q;oph\x98\xbe\xb9\x9d6|\xbf\xbf\xba}w\ +?};k\x10n\xda\xf0\xef\x1b\xb4\xafg\xf7\x0d\xed\ +\xf9\x7f\xde7\x1c\xcf/f\xdf_]\x9c\x7f\xb5\xb8\xff\ +\xdb\xc3\xed\xdb\xf3\xfb\xa6\xdf\xbf\xbe\xfdq\xbd\xe4\xdbw\ +\x0fo\xdf=|\xb3h\xbb\xce\xaa\x0a\x0d\xf2\xb5\x19V\ +\x97\x97E=g\xae3X,\xcd\xc1\xf4\xf2\xeaz\xb1\ +\xaa\xe7\xf9\xb7\xb7\xdf-\xce\xdf^\xdd4\xb0\xdf\xdd6\ +_\xe6\xf7\xe7\xb7?\xfe\xf4fqs\xde\xdcq\xddb\ +y>\x9b?,\xbb\xe9\x86Loo\xde|\x90\xfd\x8f\ +\x17o\x1b}3/\xfa\xc1\xcb?\xd5\xcb\xff\xf8\x87\x8b\ +\xc5\xe5\xfd\x93V\xb4\xdf\x11\xda\xdcF\x89\x17\xb3\xbb?\ +\xdf\xcd.\xae\x9a\xae\xbb\xc6d~{}\xbd\x987\x0a\ +2\xbb\xfea\xf6\xd3\xfd\xb3J\xad\xdf\xc2\x8e\xde\xf0\xb9\ +o\xa0l\x08Z\xd8\x1f~\xban\xe4m3\xa6\x0d\x8b\ +\xc6r\xfe\xf6r\xf9\xf9\xfd2\xeb\xb6\xd1\x85\xab\x87\x9f\ +^\xe1\xef_\xac\xe8o//\xef\x17M)\xd0\xfe~\ +,\xa2\xa5d\xc7|qv~\x00kx\x9f5n\xb0\ +&\x5c\xb2>_\x97\xe5\x1f\xff\xf0,\xfc\xdbFQ\xde\ +.\xe6\xad\xadZ\xd3\xa5\x87\x9f\xda\xae\xb9N\xc6\x17\xef\ +\xa9\xed\xdbo\x9a^4U8k\x0c?\xb4\xff\xe2\x06\ +\xc1O\x8d\xc0\xed\x05X^\x87\x8d\xeb\x7f\xff\xe2\x85\xc3\ +&\x83\xf5b\xa7\xb7wWo\xae\x1a\xe5\xe4%\x91T\ +\xd2\xa5\xa8\x1d\x19\x08\xa9\x11\xf8\x97+\x1f\x9a.\xe5\xfb\ +nv\xf7\xb7\xc5\xdd\x1a\xaf\xa6\xc1\xe7\x7fk\xc9\xffx\ +ww\xfb\x03\xfeeq\xb3\x12\xa7\xe1\xbd\xb8i\xb5\xfb\ +\xdd\xc3\xed2\xe3nq\xf9\xbf[S\x03O\xbf\xfe\xbb\ +\xfez\xff\xfe\xaa\x8cO\xc6\xf1\xd5\xa3m\xfc}\xd3\x19\ +\xde\xce\x1e\xbe\xad\x1a\xd7\xfebV\x7fT\xc2&\xe7/\ +g\xad\xdb\xd0\xfc\x9d}}\xa6\xcd\xb7\xa9.\xbfN\x91\ +\x8av\xb2\x97\xb9\xcf\xa4\x7f?{\xb1\xd6\x0b\x1a\xd3r\ +=\xbd{w\xdd\xf4\xf6\xef\x177\xb7\x17\x17M'\xb8\ +\xbb\xfd\xdb\xe2\xd5oa\xf9y\xfc9]Z\xfdWX\ +\xe0\xed\xc3\xefW\xf0L\x1b_\xe7\xee\xe1\xd5M\xe3\xeb\ +<\xf5\x94f\xa0\xbf\xb9oF\xa8\xef\xbex\xd1Z\xa3\ +\xc5\xef\xa0\xc8\xcb\xb3\xbb\xdb\x87\xd9\xc3\xe2w\x18\xf0r\ +Eq\xbd\xfc\x09\x13x\xb9\xea>+~;\xc1\xbe,\ +s\x04\xf0\x95C/\xf4}\xc8\xcb\xa7\x88\xfc\x16\xe0G\ +\x83\xfd\xd7\xd1`\xff\xf5P\xb0\x0d>/\xb0\xa9\x0b\xb6\ +\x1d\x80\xf5\xd7#\xed\xc9\xd7\x87\xda\x13\xfc\xcc\xecI\xf4\ +\xda\x13*z\x08\xf0\xa3\x95\xfc\xebC\x95\x5c\xe2\xf3R\ +\xf2\x18\x80\xfb\xa8~\xc1rP\x97C\x9d\x02\xa4\xa8\xf7\ +o\xf5\x09\x964B\xc5\xda\x8f\xf7:\x06\xe1\xad\xa4\xe3\ +\xfd\x5cC\xe4\x01?\xb76\xd6>~\xae!\xb6\xbe\xcb\ +\xfe\xac\x87\xfd\x5cC\xf4\x0f\xfb\xb9\xed\x97\xd9\xf5\xcep\ +,\xe7\xba\xaf\xbe\xbd[4s\xf3\xdf\xae\xf3b\x02\x7f\ +\x86l\x9d-Q\xe8\xf2\xd2\x9b\xc7\x8c\xff\xbc\xb9zh\ +&\xdf\xef\x9aY\xd8_\xdb)\xe1\xbf\xdd4\x13\xb25\ +\x8a\xff\xa8\xca\xfb\xdd\xec\xe1\xee\xea\xc7\xdfa\x01!R\ +\x99@\x9b\x0a\x92\x01\xca\x84\xb0h\xb6_\xa6A\xc5\xd0\ +\x13_.\xf9\xcc\x1b\x05\x94,\x80\x98\x8e\xab\x9cF\xe9\ +\xa8\x98\x0b\x83\xc72\xe7r\x83\xe6r\x83\xe6\xae\x9dE\ +;\x89ar\x8f\xfel\xaa\x08\x11\xd3\x90\x8a\xbc\x96\x14\ +\x9a\xed\xa9\x22D,\xc3*\xb2\x90\x8b\xf9<\xdfc=\ +\xa0!Dl\x1f\xd6\x90\xfa{\x84\x86TX\xfa\x10\x0b\ +\x1e\xad!P<\x125\x97\x1a\xf2\xfc\x83\xb0\x80\x85\xca\ +\x84\x8b\xa4\x13\xc5bJ/WU\xc5e\xfb\xa32\xac\ +\x0a\xff\xa9\xc9p-\x8c\xc0\x18+\x12\xfa\xe2EP\x91\ +\x10\x13Z\xd1P\x87fg\x8d`b}\xb1\x15\xbb\xda\ +\xa6\xb5Q\x98\xd874b\xdf\xc9\xf5\xd9y\x0f\xeb\xdc\ +P\x89='\xd7\xa7\xd3\x97\x0aZ\xbf\xbe\xe0\x11,\x0a\ +\x22\x09\xe7\x04\x0b\xb0\x0a\xdbd\xda~C3\x9e\xb4\xd7\ +L\xc1'\xa6E\x1d\x94'S\xc9\x82B(\x1d\xe5\x89\ +\x22\x19j\xf4\xac=P\x02QA\xd2\x9e\xd5G\xa28\ +\x93\x13?\xab\x0f\x156u\xb2\xdcC}\xe0D\xb1\x15\ +\x82\x1db+\xa4m\xda7\xb6B\x88?\xeb\x98\xe3y\ +\x04\x0dQL\xafcNH\x0aMPJ@B\xa3\x12\ +\xee\x05Q\xfdS\x18s\x98HO\xa4\x22\xe4\xc3*2\ +[\xb4i\x805n\xb2\xce\xd3\xab\x08i\xbf\x8a\xf8)\ +\x8c\x08\x14\x8e0\x8f\x09\x14!0\x91\x89AA\x08\x9a\ +L\x85\x0b1\xa8\xad+\x0cd\x84<+L\x14Ie\ +$\xaa\x0a\xe3\x85\x1d\x22\xe2Ya\xac\xb8\x03Z\xfa\x93\ +\xc2pIW\x10\xd0\xdd\x15\x86I?<\xea0\x9dl\ +\xd4a:\xe1\xa83,1\xf0!]\x04\x8a\x05\x8b2\ +\xd9`_a\xd0S\x85\xaa\x19N\xef\xc23p\x7f_\ +9\x86\x83\x96\x99\xd9:\xeb^B\xd0\xc4\x16S\x9eh\ +13\xf0\x5cLeB\x05$\x18\x9a|j\x07c\x12\ +\x22\x96\x96\xc8\x8b\x06\xb2z\xed8,\xc5\xdc\x8d\xf3\xb9\ +\xe3x\x11#r[\xe5\x5cn\xd0\x5cn\xd0\xdc-\xfb\ +\x96\xa6b\xb6\xe0\x1e\xd5y\x01\x8d^\xe7\xc5\xf1\x08X\ +\x86\x9a\x02\xb5\x8en\xa6\xae\xec\x8e\xa7\x19\xb5Y\xa1i\ +,\x13D-\x8a\xc4\x93)aI\x16\xcf\xea\xbc \x16\ +&\xe3\x90g\xe7E\xbdppf>\xfb.(E\x92\ +\x95qEC\x95f\x0f\xdf\x054>lg\xc0`w\ +;\x933\xb7\xcd)\xf5\x0e\x93!\x9e]\x0cO\xc5\x0b\ +$%\x01\xdb\x8b\x8d:Z\x9f-\x03\x87\x0d6\x0a\xc2\ +\xe8[m\xda\xfc\x82\xe6\xbb\x9aK\xb0\xd8(\x22Q\x1d\ +9\xf4\xc5\x80\xc8\xbb\x97A\xbb\x9b\xe4\xd7\x19=\xacO\ +j\x97@\xa3\xd7.\xd9x7O\x0b\x99\xb3`ku\ +\xb4\x80\xa2\xa1(\x9c\xa6{\xc4\xd6\xd4O\x14[\xd3\xcdj6h.\xbb4u\x9e#`\ +\x11\xfbD\xde\x12\x07\xf4\xe5\xf5u\xb3ti\xa3I\x07\ +&\xc5\xcb\xa9\xf9\xde|apF\x9c\xba\xeb\x04\xa7u\ +\xc7\xe0\xd9U\xa3\xe8\xfaq\xfa\xec\xe6Q\xec\xd8\xf6\x9b\ +\xd0a\x985\xe3\xcao\xfe\xe1\x1f\xfe\xe1Q\xceZQ\ +\x0ck\xdd\x9f\xde\x09\xce|\xfe\xc18C\x05\xb4\x91\xb2\ +\x9f3\xf5p\xfe\xe1\xdb\xab\x87\xc5\xe6\xc4\xa9\x0b\xe8#\ +\xdfO\x03@M\xf3>\x005-\x07\xc4<\x08>M\ +\xc7\x13\xc3W\xfb\xda\x7f\xff\xe5\xeb\x7f\xfd\xea\x1b\xfaf\ +7\x88*\x9e\x15\xe7.\xfe\xb5Q\xaal\xdd\xda\xf5*\ +\xdc\x9f\xfe\xf4/\x7f\x04x\xb1\x86\x03m@T\x85\xec\ +e\xf3\xe5\x97\xef\xb3\x91\x8a\xc8\x11\x03\xdd+\xdc\xf2\x9b\ +\xbe\x19\x22'\xee\x01iJa\xe1\x8a+\x02\x15\x16\xf1\ +\xfa\x08\xc3\x8a\xb2\xda3\xc0)E\xf9qf3^\x0c\ +\x84-r\xf0>rhG\x04^\xab}\x98\xae\xd5>\ +L\x8fU{\xdcR{=B\xed\xbdPr\xad\xbc\xb6\ +?\x8fUw\xdaRw\xdf\xa7\xee\xd6\xa9\xbb\xd4\xbaG\ +\x01\x00\xaa\x95\xb7\xd5\xef\xb3\xf3\xea\xbb\x1c^{\xff\xa6\ +\xcfc\x91]<\x96\xea\x8b\x02U\xa7\x02\xa90\xc8\xb3\ +\x83\x89\x9cE5\xf4HU\x8e-U\xf6#W9W\ +\x96\xe7\x0f\x8d\x1f\xd6,\xd0\xbe\xf9\xb7\xd7\xff\xa7\xa9\xe0\ +\x92f\xf1\x7f\xdf]\xdd-.\xeaz\xfe\xfd\x97\xef?\ +\x19\xd3\xa6|X[\x13\xbe\xbe\x22\xfcY\xda\xb5J\xac\ +\xc6\xb4?\x5c\xbdz\xfb\xe6\xf2\x7f..7\xb0ZV\ +\xe1\x9b\xabZ\xf27\x0d\xe1\xea\x9e\xf3\xa7\x9bZ\xc9\xce\ +\xd7\xb869\xbf9\xa5QEb\x87\xcf\xdb\xaaVA\ +\xe8s4\xab\xb5\xfa\xf2\xf9\xd9\xd5Zy;\xada\x1d\ +_\xfd\xad\xb5\x8f#\xfbjG\x8e\x9b\x13C\xf6\xd5^\ +0\xf6\xe9\xc0S\x97\x12\x04\x0e\xdei\x03(A\xecu\ +u\xc7\x14\xa3 a\xd6\x8eP\x89>\xaeh\xf9y\x8a\ +Vg:\xfd\xb3 \xe7}\x94\xce\x8bc\xba\xd4\x9e\xe3\ +%\x02k\xf7\xd9 ir\x18\x8a\x08bh_\x1c\xa3\ +\xb3\xee\xb1\x88\x83\xa0-\xa6\xed\xf2\xc7\x8f\xd8\xc6\x9a\xee\ +\x1f\xb5\x8d\xab\xcfs\xfc6^\xe7\x8b\x9aa\xcf\xde\x0d\ +YI\x95\xae\x8f\xc3X@\x14TW\x91\x9d\x0d\x9a\xcb\ +\x0d\x9a\xc6\x11\x8a\x92\x16\xa8<\xbc\xecK%=&S\ +*\xa9\xc9\xb1\x8c\xe3\xc3\x04J\x82\xba\xd3\xc4\x8b)\x90\ +\xeb\x04\xa1\x84\x07\xf8\xcb\xe1V8\xb9%\x0eS?\x81\ +%n}\xac\xf5\x92z\xcb\x97\xfc\x04g\xeda\x8a#\ +'\xee\x95\x13\xf7\xcc\xdd\xdb\xbc\xc6\xfd<\xa2\xf9\x0b\xb3\ +\xe8oi\x97}\xba=gQF\xaa^O\xed\xee\xf5\ +b} \x8b\x05\xc3\xf5\xf4\xfa\xea\x22'\xd0\xd7\x8f\xd6\ +\x08.v\xeaF8\xf9$\xd34\xe4\x18\x8b0<\x9c\ +\xf8iQ[\x066\x89&Y\xd8\xd0\x81p\x82\x05\x8d\ +\x9b$/\xf7\x9b\xaf\x9e:,`\x1a~\xb8\xf8\xd5\x01\ +\x88f\xd8\x1f\x19<\x18\xaf\xa8=2\xa6\x12\xd4\x07\xca\ +P\x00\x019\xeb8*\xc5\x15P\xea\x03\xe5u\x9a\xcb\ +\x0d\x9aF\x0c\x82\xe2\x81\xa4\x03\x8a\x81K\x85\xc0B\x89\ +\x86\xd0\xaa\xc7\x14\xb5\xa8'\xc7\x0e\x03\xe6\xc9cB\xf4\ +y6\xfep\xe4\x88\x22?\xedN}\xdaP7\xfdB\ +B\xdd\xf4Y\x87\xba\xe93\x0eu\xd3\x09C\xdd\xa7\xef\ +\xfdN'\xeb\xfdS\x8cb\x1c\x946\x99Z\x09\xb66\ +}b\x83:\x91\xeb1\xec\xfa\x14\xb3d\xfb\x89\xc94\ +\x0e\xb0\xf0\xcd\xce\xcb\x87\xc5\xddN\xfb\x01W\xa4L\x82\ +/\x9a\xfb\x16\x7f\x9e\xbd\xbb\xbf\xbf\x9a\xdd\xfc\xf3\xf5\xbb\ +\xf6\xfe!\x0e\xed\x04\xe6\xe2\xab\xe6P\x8f\xd9\xc3\xd3)\ +D\x9c\xa2(\x9d\xa5\x00\xebL\x99\x84W\x8b\x02V%\ +\x9f\xb4=\x04\xc4\x8e\xd1\x1eT\x04\xda\x0f.\xa6\xb6\xef\ +\x90{\xe4\x89\x81!r\xfff\xce\xacs\x16.\x12n\ +T7\x83\x89\x17\xc8\x08\xaf\xc3\x0b\x16SO\xcb:\xc0\ +P\x16$V\xb2\x83\x82\x0a\x838\x1a\x97\x94\xb4hA\ +|.s\x8aE@\xdc$\xaa\xfd*\xc4a\x18\xf9\x5c\ +\xf9)r\x09\x93Lz\x92\xb0\x12\x1d\xbc\xa4\x81)c\ +x\xcbE\xc4\xf6\xcdn\x9b8\xd4\xe0\x95\x97T\xc9:\ +\xa88\x171\xb2\xe8\x0c9^\xda\xdf\x18U.(,\ +\xe68\xbcf\x9a\x99\xf9\xd9NF\xfbI\x9c`\x16\x17\ +\x0f\xe3\x09c\x81\x0ce\x7fy0BD\xe0\x83\x081\ +\xf1\xe1\x08e\x09I\xb0:\xa7\x86\xce,\xb5\xa0)@\ +G\xa9a\x08\x94\x09\x17~\x84\x85\x8aj\xf2r\xa7\x0a\ +\x14\xe3\x0c\xf2\xc9\x14-\x0b1:N,\x8b)X\xe6\ +(td\x18\x1d\x94C\xd1!-\x1e\x86V\xf5\x87\xb3\ +\x90\x05V\xfd\xa94\x9d\xb5\xf0\x5cP\x9dx\x87\xe81\ +\x96\x5c~&p8\x0a\xac\xca\xc3\x91\xe4\xccOEG\ +\xaa\x86@\xd5\x8c\xaa-K\xc5\xe0@\xb4Gm\xc1D\ +\x1e\x85\x0e\x8e\xd0\x91O\xcb\xc6$\x1a\x89\xc6D\xa0X\ +\xfb\xd1\x09S\x01\x0b\x03\x1a\x83\x90\xe4)m\x0cS\x85\ +F\x0a30Q\x1dW\xb4\x04\x03!Y\xed?@\x85\ +\x11I\x86W\x1d;\x0a\x85-\xc1\xa1\xe5\x06JQe\ +\xf5\xc9\x94d\x14\x1e\xbe\x83\xc6\xc0\xc1V\xc5W\x9d\xbe\ +\xc2\x82$\x05\x12\xb5\xc2\x92\xb9\xa4\x09\xad\xdb\x83\xa98\ +\x85\xb1\x1c4,M\xa6\xba\xeau\xab^\xb5\xca\xb5Q\ +\x18\xe9\xa0\xcd\x11\xc6C1R\xc9\x02\x86,\x1d\xcb\x9b\ +\xc5]\xac3\xe1B\x93b\x96Us*\xcdv\x8c\xb2\ +8\x9aj\xe4bJ\x8f0!\x03br;\xb5a0\ +\xb4\xf4F\x9f\xb0(\xb0\xf28\x9c\x06m\xb3\x0b\xd1\xbe\ +8\x1d{d9\xd2\x88\xd7\x8b\x02\xca\x98\x11\xea\x97o\ +\x83IYF\xd8\xe0_\xf8\x18N\xca\xe3\xc7\xf0}\xf6\ +2s\xdf^f\xdd}\x03\xd4\xdc\xe7\x17\x17\xbe\xeb\xc6\ +9\xd1\x18\xb7\x97\x19\xc7\x9f\xa0\xd1\x07\x07\xcai\xce\x02\ +`\xb4\x93\xedo\xc68\x1d\x1cp*8\xe8tp\xc8\ +\xe1;\xdd]\x88>\xd8\x1f\x5cHv\xef\x0f\xaf\xa5I\ +\xb6\xff\x86\xc0\x9c\xe7|\x86{o\x08T\x96\x18f>\ +\xbfh:)\xeey\xd2\x84\x0b\xd9\xe1p\x920~\x10\ +N\x12\xe6\xe3\x1c\x950^7\x8b\x90\xa6\x83\xe7\x8b\x8d\ +:\xea\x0ee`\x9b\x86\x9b,\x92t\xbe\xf8\xf0Y\xa4\ +\xc3'\x9c\x0f\x1f\x87z\xf4E\xc5\x99}\xea\xef \xa3\ +\xd5\x7f\xfa\xb8\xf4g\x82$\x935u\x87\x02\x14\x9a5\ +\x90\x00\x051\xa0\x1b\xe4\x89\x02\xc6\x0a\xfe\xa4\xed\x95\xe6\ +\xc0\xe7\xaa\xeet\x8c\xe7\xaaP\x14\xc0\x98d\xcb\x83U\ +w\xe9>X\xed\x9b\x8a\x9b\x15\x22\x11\xad\x8f\x01\x95\x0a\ +a\x88\xd5\x85(\xc6\x059\x14\xb5\xa2\x97V\xc4Bl\ +\x87\x87\x0a\x1c\xc5\xc3%&SX\xb1\xf1\x1c\x11\xc1\x22\ +\xb1\xe1gt\x06\x87G&\xac\x08\xb9V\x93\x87h\xc5\ +1\xc4\xab]\x84\xc2\x86\x12\xa4V\xa37\xbe\xc2#~\ +\x06@\xe4\x94\x80\x84.\xf1\xd0|\x06Dr\xa9\x1fN\ +\xd5\xc5\xb0G9\xea\x10\xeb%\xad\xa5\xd9\x0d\x0e\x9d\xc0\ +(\x04h\x18\x01\xa0C\x110-\x22\x81HU%\x9c\ +K\x068V\x04\x88\x8bk\x98\xd4\x1e\xe2Y\xd0\x89d\ +\x8c\x5c0,W\xea\xcf$\xd7p\xcb\xa6\x8fkV\x8e\ +\xd1\xe2\xf7\xac\xf0\xb9k\xac\x9c\xd5s\xd3\xac\x1e\xeb\xf2\ +\xa8\xb1\xf3\x9a=\xaf\xd9;,0E\x0eN\xca\xe5\x02\ +S(\x8a\x1e\xea\xfb`\xb0^W&\xb6\xbeU.\xf2\ +\xcdx\xc9\x91\xb0\x88\x10r\x05 \xa2D\xa0\xf3'\x06\ +\xc3\xa0\x8d33;\xf8\xf1\x8e\x17\xf2$\xae=\x81\xc8\ +\x8au|\x03\xe5\xe2!\xa8\x5cc\xd2\xc4\x85\xc9\x91V\ +>\xd3\xdb\xbb\xc5\xec\xe2/\x8b\x87oo\xdb\xda/.\ +\xdb\x92w\xc2\x0aJ\x18\x82\x18\xb7HEAC\x8e\xe6\ +k\x116\x0c\xf3Q\xfd\x87N:RZq\x05\xef\x0c\ +\x0c\x1e%9\x99+l(\x85\x5c#\xaa\xf9p)\xae\ +\xfd\x90\x8d\x90\x96\xf2\x84\xd6\xc2\xa0\x9eif\x85E\x14\ +\xeav/\xa7\x02\x04\x81\xb5\xcf\xac\xd3\xcc\xbb4C\xee\ +*\x14\x0d4\x0a\x9d`qs7oc\x00T,\xc3\ +HV\x01\x81\x12\xe1\x91\xd1d\xb3\x16\xf0@\x9bhI\ +\xc1\x84\x941\xbd\x8c|\x10B\x91\xfc\xb5\x97u\x84\xa3\ + \x1b\xe9|\x8c\xefeZ\x98M\xa5\xd3\xcb\xb8\xb8\x90\ +q\x9c\xa6\xa7\xf1.ve|O\x0b+A\x96V{\ +\xda\x14\xad\x98\x85D=\x86p\x9dh\xbeF4\xdc\xd7\ +H\xc3\x0c\xbc\xf5\xc4\x95\x90\xc4\xa5\xfd\x1a\xe4\xc2\x22\x13\ +(\x82`\x808a\xa0\xa2\x9c\xa2\x13\x85\x02\xaa\x9a:\ +\xaa\xa3\xe1I;ZA\xd5\xd4\xe88v \x85t\xcd\ +\xabK\xb0\xe0\xce|N\x8a\x0e\xc1\xf5~Wb\xd7#\ +\x8dT8h\xbbM\xe0H\xcfS)s\xf0\x89\xd9\x85\ +\xb7i\xdf\x98v&\x1d\xe9\x81\x07n\xb2\x1e\xf1\xc4\x8c\ +\x89}Pb]\xd8\xde\xcf\x08)\xf0dGB\x93\xe0\ +\xe1\x12\x9b\xb9\x0cI\x0c\x80&\xb1\xa7\xc4fn\xc3\x12\ +#\xcero\x89\xcd<\xc6HlV%^ck\xb1\ +\xc7I\xc4\x97s\xbc\xa4\xfd\x1f\xe3i\xf8\xe2\xf5\xfe\xc7\ +\xa42\x06W\xe6\xef\xc3\x01{\x1cd:\xa7\x99n4\ +\xe6\x188e\x97\xe3\xd4/.\xe6{+\x90\xec\xd4e\ +.._\xef\xaf@\xe2c$f\x1c\x92\xd8\x81\x1d`\ +o\x89\x99\x87%~}\xa9\x0b\x80\xbd%f\x1d!\xb1\ +\x88\x9dd\xe9\x84\x89\xc4\x89\xcc\xa2\x89\xc2\x18\x89a\xd8\ +,\x06.x\xb1\xb7\xc4`\xc3\x12s\xf8\xc5\xc6\x03\x94\ +a\x89!N}\xc0\xac\x99Y\xdf\x9ee;\xca#8\ +NSR\x9eX\xc9`\x14\xc5\xf6\xdc\xff\xa9\x17\x0f\x94\ +\xb4\xf6\x07\x16U\xc3\xe0\xd6\xa5\x22*A\x868\x99Z\ +\x16\xcb0\xb4\x97\x1f\xf0\xe4\xdf\xce.\xbaG\xd0:\x00\ +kg\x03\x12p\xd1\x80\x1a\xdaY'\xb9|\x9f\xe4n\ +I\xa1\x9a\x12#v\x94\xf6;o}*\x89P\x1f^\ +\xd7\x90rD\x9d%J\xdd\x1b\xb4K8\xf5\xe8\x95\x17\ +\xeb\xad\xbcR\x9d\xaab\x81\x94\xcc\xc7\xda\xaer\x100\ +S\xaa\x1f\xae\x05#\x91\xb9\xfbT\x19\xc0Cw\x91\xab\ +\x7f.7\xbc\x83\xd0\x8b\x86CZ\xb4Z\xc7\x01&J\ +/?&R&\xc7\xd8y\x07E\x90\x12\x98&P\x0c\ +\x93U\xe2e?.{\xb7\x0dYD\xac\xb5\x8d\x03d\ +~L\x9c\x1c>u\x9c\xb8\xa4!H\x85)\x0a\xb1\xa6\ +\xf8G\x85\x89~\x85\xa9\xe3\xb8\xf5\xfat\xf5\x91\x9cj\ +\xe1V\xd6\xba\x03\xb4>\xfa\xd9\xb8\xbaZ\xea\x91\xc8(\ +\xfe\xf1-\xae\x09\xf7J$uY\xb8Q\x01\x0eG\xea\ +F\xc2\x12\xd9\xf1Q\xac\x0d\x92VP(N\x91\xa1G\ +\xd1\x9f\x09\x1f\xdd\x88\x9aK\xaf\xf0\x01\x9d\x88M\x814\ +w\xad\xd1S(\x1a`\xe2U\x07\xb3$\x0bs]\xe8\ +\x15Q\x8cY\xe2\x93\x1aF)p\x9f\xf3\x8b\xac\xb3B\ +\x89\xac$\x9b\xad\xafP\x12q7\xaa2S\x01\xcaP\ +\xf9x\x1211\x1e\xc7>\x91\x11\x1b\xe5\x04\xeb\xca\x15\ +\xcdB\xa4\x01\xde=C0\x9d\x09k\xcbW\x9a'\x14\ +\xc4\x0aQ\x84\xe6GE\x81\x8f\x84\x023Q\x8bBI\ +TH\xe9\xa0\x81\xa8\xc5\xcd<*\x1aX $\xa1z\ +\x90\x8c\x85;4\xb4\xb2\xe0\x8c\xa4G\x06\x83\x89\xbd\x17\ +\x0c\xe1z\x04\x97AIQR\xac\xa6\x0b\xb4\x04\x83\xd6\ +\x11\xa4!\xaa:<\x8c\xe3\xc7\xb3C\xe4\x01\xfb\xf4W\ +(\x02\x04V-\x95I\x017\x94\xfaP\xa3\xd2TW\ +\x1fJ\x1a\x11\xca\x91O\xa8\xa2L\xe8=\xe8\x1a\xf2\x08\ +\x0b\xce\x0a\x82@\x84N\xa0\x04\x05\x98\xf9rS\x92\x89\ +KZ\xfb5\xc2\x98\xc9'\x98XD\x8cxbTH\ +9\xc1\x86\xdc\x8fv\xaeg\x5c@\xd0]\xeb3z)\ +\xe9)\x10\xf5\x99$\x17U$\xac\xcf\xfa\x15\x8b&e\ +\xd6w1q\x11\x177Z\x02<\xbf\xbez\xfb\xef\x8f\ +\xaf\x81\x7f\xfa\xbe=\x88\xffD\xc5HY\xdf!\xbf\xfe\ +\xbe\xf7W\xef\xee\xae\x7f\xf7\xdbu\x94\x19\x99_\xfe\xbe\ +\xbdZ\xa3\x00\x8f\xef~_\xbd^\x7f\xfd=\xf0V$\ +\x15\x00B\x9e.\xb4\xed\xd9\xb4\xfc\xab\xbb\xdbw7\x17\ +\xdd\xcc\xffs{u\xb3\x9e\xfb\xddU\xb3\xf4\xed\xfa\xaa\ +\xf9\xdf\xab\xe7\xdb/f\xf7\xdf\xce\xee\xeef?=\x96\ +VsW1\x88WRP\x1f\xb3k\x0d\xbb/\xc3\x9f\ +bHq6\xd1\x09yIa\xd7\xb3/\x9b\xdc\x94\x92\ +\xaen\xcf\xb9x6%\xd0\xd2\xfc\x99O\x18K`\xa0\ +[\x93\x89T\xd2\x81l\xc2Y4\xc8\x8dZ\x06DP\ +\xd2]b\x22^42\x93\xdbL)\xa1\xa9\x13\x8dB\ +D\x86\x8fy\x06I>\xb1(\xee)\x90g_w\xb3\ +\xd3J\x10\x81`\xcb\xb6f#@\x09g\x8f6\x13\x0b\ +\xabx\x9b)\x05)\xa8\xcdD/\x0cM\xaa\x99-_\ +T*h\x19]\xdaV^\x89\x82\x18\xc8]\x16(R\ +\xc2\xd8\xb8\x16\xd6\xcd\xac\x15\xfb\xba\x9b]\xc5\xf8r\x95\ +m\xc8\xf2,1\xad\xca\x92T\xc1.4\xa8V\x14\xc8\ +d\x1dF4)dN\xf4\x0c96\x99.E\xd2\x91\ +\x9f\x1aG\xf2\x83\xed\xf8\xf7\xb3\xb5\xf6\x15.\x12\x08(\ +K\xc6\x1e\x85\xd3!\x9f\xb3\xb5\xcd\xa4\x02\x12\xe8\x13\xd1\ +\x92\xe8-[\xf3\x82\x1e\x81\x13\x85\xe2\xe1\xa4\xb6\xaa\x17\ +\x156\xe7\x9c\xa8\x16\xcd\xb0hE\xc8\x92\x94\x0d\xa9a\ +\x09f\x0eZe\xb6w-\x9b\x17\x12\x80\x96p\x11d\ +Q'\xc0N\xf6\x97\x8f\xd9(n\xcf,\xb0\xcd\xf4\x82\ +\xa0\xfaT\x94g\x9bG\x85R \xde\xabVz\x11\x12\ +\xa7G\x01\x22\xb5\xcd\xc4\x02\xae\xf1,)\x7f\x18\x95\xbf\ +w\xc2u\xad\x05`\x5cy>\xddu\xc47\xb7\x17\x8b\ +v-qcJ\xe6\xf7\xf3\xe5\xe7\xfe\xf1\xbfUd\xef\ +\xc9\x92\x9c\xdc\xccW\x034\xde\xccC\x914\x04\xb5\x09\ +\x14\x11\xc8\xa0lm\xbb\xb3\x81\xa6L\xa6O\xc6\xdf&\ +\xd3\xcc\x82\x82\xe2\x93Xzj\xa0\xb1\x8b\x99w/\xca\ +\xcc\x8e\xef\x9by\xac\xeb\xd5\x22\x0a\x08\x05\xe4\xc70\xf3\ +\xac\xd5\xcc\xef\xd8\xbe\x9b\xda\xe1k\x864\xbd0a\xe0\ +\xba\x1d\x05/j\x88\xd4\xb5\xa3\x88^<\x11\xb8kG\ +\x91\xb4\xa8\xaa\xd1{\x06\x80\xb9\xa8Q\xc8\x9a\xb1`/\ +\x22\x12V\x0d\xe9*\x13S\x8c\xd7\x0ci\xcd\xee\x1a\xd2\ +\x9a\xbdn\xdb\x98K\x0a\xbcg\x05)KDp\xae\x1b\ +R\x93\xe2\xaa\xc4\xbanH\x0d\x8a\x03Kj\x97\x85\xb6\ +\xd5\x8dL\xad\x85u3\xbb\x86\xb4f\xaf\x1bRm\xab\ +\x1bn\xda\xb5\xa4\x86\x05\x02\xdc\xb5\x0b\x8ee\x81\x08\x00\ +\x5c\x07\xd2\xad\x84\x92\x06v-ix\x81 I\xaf\x96\ +\xb46\xe4\x9a!\xad\xd9\xebv4\xa1d\x90ht\xed\ +hH1fe\xadv\xd4\xb3\xb8\x09\xba\xad\x1b,\x97\ +\x92J\xae\x1d3\xeaT\x14\x15=\xab\x19]f\x8az\ +\xb2\xac\x9bQ$*\xa8\xce\xde5\xa3\x8f\xd9\x00H]\ +3\x8a\x98\xc5\x12\xc2\xbav\x14QJx\x8a\xbcgG\ +\x11\x0a @\xac\xd9Q\xe0b\x86h\xd5\x90VT\xd6\ +\xec\xe8\xae\xbeS~~\xbe\xd3\xcfe\xe5\xf3\xa3Zy\ +\xc4\xe2d\xc1\xfa\xf3\x9a\xf9Op%;\x11\xd9\xff\xf7\ +K\xb9\x8d\xf2\x98K\xb9\xc7\x9f\xaa\x99O[K\xd3I\ +'\xc8\x5c\x00\x80&\x04%\xdc\xa8\x95t\x85\xae\x16\xec\ +\xbc\xf6w\x0a\x85P\xa2>x\x8cg\x81\x9f\x84\xe9y\ +#vP\xff\xfa\x0d\xa7\xaf\x90\xbf\xdax\xca\xdc\xf7Z\ +\xe7\x90~V\xac\x7f\xfc\x12\xbf|\x9fUaU\xee\xe7\ +g\xfd\xfc\xe0O\xa9\x10\x1b\xfc\x0cS\xfa\xf9\xc5\x16~\ +\x10_\x02l\xf0s\xf5~~\x09\xdb\xf8\xf9\x1f7\xf8\ +=\x9a\xdc\xaa4\xfb\xac\x16\x03\xea\xd9\x88\x09\xb2\xc7y\ +#\x17\x8b\xc5\xd0\x81\x00]\xd6\xb6\xc7.wZ\xc4\xe2\ +bc\xe5\xc4\x88\xf5q\x06\x87\xecK5:\xee\xf9\x08\ +]\xd62\xe2\xf8\x95\xd1p\xa4\xf6H\x9c\xbe\xbb\xc4\xc6\ +\x8b\xdcC\xe2\xcc\x9fQb\x91<@\x01Dq\x10\x8e\ +\xe1Wm\xf7\xb0\xe6qo\xf1\x86\xdf\x8f\xda\x99L\xce\ +\xb2}g\xb2I\xbb1\x99\x8a\xb5\x1f\xef\xdb\x9e\x8c\x14\ +\xcd%\x93Cw'C\xbd\xfb\xa0\xad\xc9u\x1b#\x14\ +$P\xab\x11g.\xa8\x09\xack\xcfp\x1d\xa1z>\ +\x95fp}\x0f(\xb7\x9f\xa7\x01\x950St\x82X\ +\xdc&R2I\xc8\xc6\x9c,\xca1\xbc\x97\xc7\x0f?\ +\x03P\xa2\x10\x12*\xd6G&Z\xd4\xa2\x0b\x8feI\ +b\x88X\xdbe\xc0I\xf6)\xc0\x03\xa3\xe1\xf9\x05+\ +\x8f\xc7 :lx\xb0\xf2H\xa1\xf0.<\x89m\xad\ +\x1d\xac\xbb\xd4\xa0\xbev\xba\xbd\xee\xa1$;l\x96&\ +\x98\xc0(\xd1mPt\x01\xf8e\x8a.#D\x1f\x9c\ +s\xd5m:\x89%@\xa13]U/\xa0\xa1(u\ +\x02\x96\x05T\x03\xf2y\xde\xa0\x5c\x8c\x81\x86gcE\ +1Sr\xe2\x85\x15\x938\x17\xd3\x98L\xa3\x801\x09\ +k\xfb\x0b\x0b\xb9\x13\xa2L\xa6\x04\x85H\x9c\xac\xf9\xca\ +%\xd3\x8c\xf3\xf0I\x1b\xb1\xd3p\xd7\x918\x10D.\ +\xea\x81\xa4uo\xa1\x14e$\xad\x93X\xec\x92\xcc\xdf\ +'\x99wI\x06\x80\xb4\xa2\xe0\x19\x96\x8f\xd3[EH\ +\xf4\xc9\xd4\xb8\xb4\xe0\x19N\xa6L%\x1d\x10u\x14b\ +'5\xc5i\x85\x8dA;+\x97\xa2\x18\x18v\xb6\x11\ +2\x15P\x0c\xab\xbdN\xb8x&\x90\x1dh\x8b\xbd\xa8\ +\x98\x99\xf9q\xec\xb1\xed\xb2\xb1]\x0eV*T\xeb\xe8\ +\x14Z\x81\xb0\xaaR\x8e\xc5]\x89\xean\xd5.\xc5\xbc\ +K1\x1cD0\x85@i\x90\x22\xc3TX\x9e\xad\x9e\ +\x04\x98!\x13.\x9c@\x086A/\x0c\x09\xd6\x5cV\ +/\x96L\xe3:\xa5\x0d\x1bu\x8b<\xd8\xa8\xb7pd\ +pU\xb1\xb4\xc2\xceH\xdc\x89\x92A\x11\xf2\xea\x0cU\ +\x9aA\x15CwR\x89\xc7n\x08\xee\x84\xe1\x13+\xaa\ +J)6\x99JIAs\xc0Q:6l\xfd\x15\xf1\ +\x97\xa3c\xccG\xd61\x1a\xd61;X\xc7(J\x0a\ +%\xd55Jb\x85HS\xde\x8b\xc4rx5c\x8e\ +\xc5\xc8\x04\xe4@\x1d\xc3\x12b\xe4\xca\xc7\xd21\x18\xd6\ +1\xe0_\x07\xc7\x8a\x98\xf0.\x83\xe3\xaf\x96\xbf\x17?\ +\x1ck\xf9\x7f\xf9\xbd\x92c\x97^\xf9\xab\xe5\xef\xd31\ +\xd6SX\xfecz\x05G\xf2T\xc6\xe8\x18\x8d\xf4.\ +~\xf9N>\xed\xe4\xe4\xff:\xfd\xde\xda\x17IFL\ +\xbf\x07\xfa\xe2p\xe8e|\x94g\x8c\xfe\xe0\x88\xf0\xcd\ +\xe7-:\xc6IE\x1f\x1f\x86\x1d\x1f\x14\x1e\x83\x8e\x8e\ +\x8a\xe6\x9e\x1e\x9d\xf1O\x14F\xc13\xe2I\xc9/_\ +y O\x81\xce\xcfi.8$N\x19\xe3w-N\ +\xe1\xdc\xd9V\x07Q\x1cD\xb8\xcadU *\x0e\x80\ +\xa6c\x04\xb2a\x81\x92\x0f\x15\x88\xb2h0j\xe7\xdd\ +\xc6Y \x08\xbd6\x92[1[?!\x0a\xad\xa0\x90\ +\xd1\x18\xb9v\x18\xcd\xc3\x0e\x95+l\xe9\xe8zV\xe5\ +\x83\x22\x18\xa8TMNafJ\xc4G\xb9*\xcd(\ +\xb9h\x84E\xfeT;\x15|\x9c\xa7G\xc1E\x11C\ +\xad\xbbu\x89\x04\x11\xb3\xba\xafV\xc8\x05\xb53\x89\x84\ +\xa2\x16.8|\xf2\x06QF\xda\xd3\xfb\xff\x1d\xc0\x01\ +'S\xa2\x92\x14\x99\xb6\xfc\x8a\x9e\x181bq_p\ +\xfc\xfa\x9chO\xc4\xec\x84\xa10)\x9a\xec\x150\x91\ +\x82\x99a^\xb5\x8c:$\xf3\x0d\x92y\x87d\xf8%\ +\x14f@*\x13(\xda\xa8S\xd1 \x01\xf5\x89\x14q\ +\x0a\xf5\x98 Iaso\xb0\xc3\xc8Bi\xce\xa3\xb0\ +\x93a\xec\x98\x0f\xc4\xceKZt\xb0\xe3(\x22l\xdc\ +\x85\xae*\xda\xfa\xe5y\xbd<\xd4/\x059Z@\xa0\ +\xa0\x01\x9bf\x03\x9f\x00'!O\xb8\xb0\x87\xa1\xb5!\ +1(\x98,\x18\xcb\xd7\x9d \xaa\xe68\xb5\xa3a\xbb\ +F\xf6iE.\xba\x07\xb9\xc8\xa83\xb5\x83a\xe7\xa8\ +\xc4x\xcd\x89,\x8a\xeeI]\xd5\x09\x85\xc8\xba\xd8}\ +\x9df\xde\xa5\x19\xd4!\xcaTamu\xc8\x15(1\ +&X@\xdd\x89\xad\xf9\x16L\xc0m\x97\xc3\xa5M\x83\ +\x9cL)\x8a\x90\x1b\xe0\x18\x15\xa2\x1d\xfcM\x8e_-\ +\xd7\x07\xb1\xb3],\xd7\xaf\x0f@z\xf1\x93]\x1e\x80\ +\xfc\x8a_/~\xf4\x09/\x1d\x18a\xd7\x09v\x09\xc8\ +\xff\xaa\x17}z\x811\xe2\xa1\xcfg\x88\x9f\x1e\x1b?\ +\xfb\x84\x97K\x9c\xde\xadB9\xce3\xd7\xfaF\xa9\xee\ +K\xa3\x1e\x8d\xd6\xb7\x8b\xab7\xdf6\xf4X4\x83L\ +Wp>\xee\xc9\xca\xc4\xe7-t\xcb\xdd\x9f-]\x02\ +'\x03\xae\xea\xb6\xa4\x13\xc54\x05\xdfZ\x95\x8dWT\ +\xf5\xbfd\x8a\xe4\x83/\xa8\xc2\xc2\xe2\xa6\xabh\xd1V\ +\xa1\xeb\x1b\xaaz\xa4\xb7\xc0\xf7\xa4G\xf5\x14\xeaJ\x0f\ +\x1e\xe9(\x86\xeb\xe2\x93\x88\x07u\xc5G\x22N<\x9a\ +\xf8\x16\xdc\xf3~.&L\x94\xf1\xd2+\xf3\x91\xea\xaa\ +\xac=M\x05\x06\xc0\xaa\xe3+\xcbiG\xaa,g\xf4\ +\x00+\xb9_E\xd7\x0d\xd5\x90\xf5\xc4`\xc3U\xb0h\ +\x8aE\x8c\xd2|1]\xd9P\xa3e\x1c\xc4\x0bi\xec\ +\xea\x04\xd7\xa3\x0f\x88R\xbf\xc1o\x9e\xc7\x80\xa9\x81G\ +a\xad\xd3j\xb6,JBO\x1b/I\xb9\x00F\xdd\ +\x18\x5c\xefy\x1e\x17\xc8\xb4p\xf6\xbc\xc17F\xeeN\ +\xea\xd9\x9c\x84p\xf8N\xad\xcaw\xf6\xea\xbb\xab\x8b\x7f\ +o6\x9d?\xfc\xb5\xb9\xb8gE\x879\x14\x1d\xc1c\ +P\xac?-?;\xed\xb1\x1c\x1eyB&Ss\xa9\ +\x0f\x87\xcc\xa5\x86\x0f8\x8a;s\xd4\xd0|\xd6\xa0|\ +\xbdz\xf0\x13\xa1D\xf9\xa0\xf2P\xa2\xf5\x03@\xd6\xa6\ +\xa1S\xa2{\x14\x88\x12\xa3\x9f5c\x9b\x86X\x17D\ +\x92^\xfe\xb4E\x83\xe4\xa2M\x83\xfc\xd9E\x01\x00\xfb\ +\xcb\xa0\xfe2\x14\xda4X\x86\x10Cff\x7f\x19\xb2\ +\xa5\x0co\xd3p\x19l\xb1\xbd\x8c-\xcdl\xd2\xa6\xe1\ +2$\x07\xe4\xd8\xd2\xde\xaem\x1a.\xc3@{\xf9\xf3\ +\x96\xf6\x8eY\x9b\x86\xf9;\xe2V\x19xK{\xcf\xb8\ +M\xc3e\x04\xcaV\x9d\xe2\xfe\xf6\xbe\xbf\xba\xfe~q\ +7\x5cD\x22n/\xc2\x06_\x1f>\x5c\x86\xf0v\xa8\ +b\x0bT\x97M\x1a.C\xd1%<\xa2\xb7\x0c\xd9\xd2\ +\xe4)m\x1a.\x83I\xb7\xca!\xb4Em\xe7m\x1a\ +.C\x153s\x8b\x1c\xb2\xa5\xfbi\x9b\x86\xcb\x08Q\ +\x01\xf3\xfe2\xb6\xb4\xb9R\x9b\x06\xcb0\x08\x03\x00\xea\ +/cK\x9b\x0b\xb7i\xb8\x0cA\xdb\xaa\xbb\x8a\xfde\ +p\xb6i\xb8\x8c\xa4\xd0 \xea/\x83\xb7\x94\xc1\xc3B\ +8\xa7omp\xd5\xd1c_dDfz\x7f\x19\xbe\ +\xa5\x03\xda,fC\x9d\xa3\xe7\xdd;\xe3\x0e!\x19\xf6\ +~\xeeV\xaf\xa4T1\xeb\xac]\xa4\x92ikk\x17\ +\xa3\xb8\x93R\xdf\xb2\xc1p\xfb\xb0O\x13\x1e[\xac_\ +\xb6\xe9P\x9f&b\xdba\x18\xde\xa6\xc1v\x05I\xe9\ +\xe7O\xfd\xfc_g\x9b\x06\xf9#H\xf6\xf3\x97-:\ +\xb3h\xd30\x7fa\xc2$\xee/\xc3\xb6\x94\x91M\x1a\ +\x96\x81P\xad\x9f\x7fl\xd3\xfb6\x0d\xf3\xb7\xb4m\x83\ +B\xe4\x96v6o\xd3\xb0o\x99\xb1\x85?\x1d\x81\x7f\ +n\xf5]#\xb7\xf9\x95\xd2\xa4]|\xbe\xe0\xed8m\ +ik\xc16\x0d\x97\x91\xaa\xdb\xcb\xd86\x8f\xd06\xed\ +\xe0\x04dDj\xbf-\x05\x18m\xaf\xd5!M\x89\xfb\ +\xcb\xa0-e,\xda4\x5cFJ\xf4\xf3\xdf\xd6\xde\x97\ +m\xdaap\xd6\xed\x0e\x00lio\x876\x0d\x97\x91\ +\xbc\xdd\x07\x87\xd8R\xc6\xeb6\xed0>kl\x1d\x9f\ +\x11\xb6\x94q\xd9\xa6\xe12\x06lH\x22\x8d\xd4)<\ +U\xec\xc1#\x0b\x12\x81o\x89@\x98{\x09w\xc0c\ +\xc6!\xc2\xa8g\xcc6\x19=\x99\x07\xd82h\x98\x8d\ +6\xb8\xc8\x12\xfd\xfcc\xcb\xc0Mm\xdaa\xe2\xc8\xfd\ +\xc6\xdca\xcb\xa07\x1b\x9e\xf9\x1a\xe5\xd6\x1e\x11N[\ +\x0a\xc06\x0d{\xacJ\xb1uD\xf2-\x8d\x9c\x17m\ +\xfa\x84=V+@,u\xe9\x9dbA`\xa5\xf7<\ +V\xe6\xe8\xf5XUz\xb4_m\x8b[\xf9\xbaM\x07\ +{\xac\xbaE1)\xda\xb4\xcb\xe8\xc9\xdbF\x850\x18\ +\xd1\xae\xa3O\xb2K9\xe4\x5c\xb7\xb4\xdd\x03\xe7\xec3\ +\xa1\xf9\xee\xa7\x9c\xc5\xee\xb1\xf3\xd7\x97\x97\xba\x11\x93\x1f\ +q\xc8\x19K\x1c\x80\x06+\xec\x8e\xc6\xbc\xa93~\xa0\ +\xca=\x9ci\x9f3\xce\xe4\xe2\xa8`\x18\x1e\x02\x86\xf1\ +\xee`,..\xebK<\x07U\x83M{\xd0\x18f\ +=\xfe\x00<\x0e;\x04\x8e\x88S\x9d\x80\xc8\x09?\xe7\ +y\x80\x00\x07\xc0!@{h\xc7\xffk\xef\xda\x9a\x9c\ +8\xb2\xf4\xf3\xfaWt\xf0F\xacH\xce\xfd\x82YG\ +@\xdbLL\x84\xbd;\xb1~\xd8G\xa2\x01\xc10\x03\ +\xb4\xa3il\xe3_\xbfu\xaa$\x95J\xa8\xbaQ\xd3\ +=`\x8f%\xe3V~Yu\xf2\x9c\x93\xe7\x96\xa9K\ +\xea\xf3\xe7\x07\xfc\x1e \xc8\x8c:fI_\xa7:\xc8\ +\xae\xa2\x0e:\xc0:\xca\xbf\x0fP\x07\xc3a\xa1\xe3z\ +\xd5\xc1q\x15u\x08\x1c\xa2\x0e\xc2\x03\xd4!\x07ER\ +\xc2\xebUG\xf2U\xd4\x91zS\xb1C\xd2\xffE\xb1\ +c^\xe6\x19])\xf0\xe5\xe7\x92\xebs\xdc]\xef_\ +r\xfe.+|\xc4\xa9\xd2\xc2)\xfc\xf4b\xca\xf8!\ +e\xff\x04\xdbP\xbcJ\x9aU\xe4\x83\x5c\xe5\x04\x9e}\ +\xacm(\xce\xa4\xd9y\xd2\xd7\xe8*fy\x05u\x98\ +\xe3M\xb9\x8a9\x7f\xa9\xaeb\x91\x97\x9e\xce\x0d\xcf\x9f\ +\x07\x1c\xe8*\x96X,\x1fN\x19.s\x15K\xfe\x04\ +\xdbp\xbcJ\xcd\xe1H7e\x1b\x8e\xf2\x19K0g\ +\xbe\x8a:Xo\xaa\x04s\xf6\xc3J\xb0k\xfeIf\ +\x06\xcc\xdf\xe5O2_\xfd\xbc\x8f\xf1\x84\x04\x7f<{\ +\xac\x82_\xcbI\xaew0[\xd6\xa3~\xdc\xe4\xf6\xf8\ +\xa5I\xd8:P\x1d\xa91\x88o\xf6}8\x9bj\xe8\ +5\x89\x18\x17\x88\xe8\xb7\xae\xe3\xc0\x87pbY\xffB\ +A``P}\xab\xa7\x19\x07\xd5\x17a\xad\x05[=\ +\x0f\x93\x7fz\xa2\xe8\xd5\x15\x90\x8fgS\x81\xe7\x01\xe7\ +M\xa64\x96q\xbb\xbad\xe8\x80\xadcq\xc3\x9a\xb2\ +\x8e\x87\xe2\xa6tm\xb3k\x12\x03a^\x8e\xc0C\xe4\ +\xd0-\x11x\xc2}\x98N\xb8\xaf\xf65q\x8f\x17p\ +\xcf\xd7\xc0\xbd7J\x1e\x99\xd7j^\x17\xeft\x01\xef\ +z\x08\xef\xb6\xc5\xbb\x8c\xbcG\x03\x00\x1a\x99\xb7\xbe}\ +\xddg\xc8:\xfa\x9c\x18N\xbca\x92\xbd!b\x1an\ +xUn\xcc$\x80\x1b\x8e\x11\xb0\x83Ds\xc5\xf3x\ +\xcdGL\xe5\x90\xad\x9e-\x9f\xbf\xfd\xe6\xfex\xd6\xdb\ +\xc9\xeb\xe5\xb3\x9f_.\x7f\x99\x88\xf8\xcb\xcb7\xcfN\ +\x7f\xb9\xb3\xfe\x88\xbe\x85\xdf\xda\xd7\xbf\xfe`>\x10\xed\ +d\xb2\x93\x17\xcb\xee`\xa9\xee\x9a\xff\xba\xb5\xa7k\x95\ +A\xfbwr\x06\xbe\xdf\xbd\xec\x8e\x9c;}\xb5\xec\xe2\ +\xdc\xd3eQ\x5cw\x9cuC\xec\xc1O\x9f\xfc\xa3\x9b\ +\x82}=ON\xcf\x9e-\xcf6#\xe0\x04\xee\xb3y\ +7C\xd6?\xfa\x8e\xe2g\x0d\x0fE\xcff\xb2\x9e\x9c\ +\xbc]N\x99\xff\xed\xf4\xb4\x0b\xc1\xd4T\x89\x90u\xda\ +9|~\xbb)\x09\xa4L\xbb*\xdc\x1a5\xf5\x88\xa0\ +\xbd\xaa\xac\xd8l\xb0\xb7\xeb}\x91\xf5\x1d\x82\xef\xce\xce\ +\xbay\xbe\xf3\xea\xe4\xfd\xb2\xe3\xfc\xfb\xfa\xf3x\x95\xc0\ +\xff~\xfaK\xa9\xad\xb2\xe1\xbb\xe5\x08\x95\x8a\xdf\x8e\xe0\ +H\xab\xef\xb9\xf3\xe4\xc9\xe9\xaf\xab\xde\xad\x12\xa6\x08\x8d\ +u\xd3_\xba\xd6\xa3\xb3\xd3\xd7\x7f;[\x82\xd8\x8f\xcb\ +\xf3\xf3\x97o^\x94\x03\xd4c\xa8\x17~}_\xb7\xac\ +\xa0\xa1\x90\xa9\x1a\xe3\xa7_'\xd0\xfbm\xe8m7W\ +\x1d\x9d\xd2^33\xdf\xedx\xffA\xc7z\xbe\x00\x00\ +j\xbezp\xf9\xfa\xa7\xbd\xf8\x96\xb1\xd1x\xe9\x16*\ +#\xba\x1a\xb1\x1bp\x8d\xbd9y\xf2j9\xea\xb2\x1e\ +?\xbf|\xfb\xb2\x03\x07lp\xaa\x0f\xdd\xe9\x9b\xaf\xee\ +\xbf^\x9e\x9f<;9?Y\x9b\xd3\xba\xdd\xed\x18|\ +\xf5\x1f\xf7\xdf\xbc\xbd\xf7\xf6\xf9/\xdd\xab\xe1\xe5\xab\x97\ +O\x97o\xab56{\x17_\x17\xbc\xa5\xc4\xe9\xd7f\ +(\xc6/\xc7\x8c\xcd\xfe\xce\xbf~\xdb\x01\x8c\xe6\xc2\xc4\ +R,\xd60w7\xe3LF\xfd\xf1\xf4\xdd\xd9\xd3\xe5\ +\xc3:)\xee\xed\x15\x86,\xd7\xea\x1c\xf1\xf5\xf7\xcb\xe7\ +\xe7\xff\xd3O\xee\xa8\x98\xd50\xa7?\x9dw\xa7\xce\xfd\ +\xd6\x7f\xe5ce4\xa3\xa8\xe7'g/\x96\xe7k\xb8\ +\xe8M\x91\x92e`\xa3\xfb\x06\xc8\xf2QW\x03\x9dt\ +\xac\xfc\xed\xbf\xffB24J\x9b+Z[h\xddP\ +\xd5`7\xa3\x9d\xa3\x94\xffmfp\xf8&IM\xea\ +\xf3\x93W+\x0f\xef\xee8_\x1e\xaf\xcc\xe7Q\xff\xe8\ +\xf17\xa7?lz\xc6\xeb\xcby\xea,\xbd\x93\xa7#\ +\x95\x9e\x8b\xd2\xf2\x94\x8f\x82\x0b\x1c\x05\xbd\x82\xfchi\ +\xceJ\x8a\x7fDE\x14\xe8d\xff\x9c#\x9b\xa1\xe8\xbc-\xc6\x838\x9e\ +\xd0\x13\x5c\xd3\x9b\x99{J\xb3\x88\xf9\xf9\xaf\xe7\x94\xa2\ +\xcf\xcf\x1a\x1b\xcf\xcf\x7f=\xa7\x94\xf2\xba\xe6_q\x96\ +\x922\x1e\xc2\x93\xf2,\xa5\x10\x99\xf7\x93\x87\x1f\xe8I\ +u\x86\x12\xce\x12\xc1\xcemyJ\xc4\xe7\x0c{f+\ +t\xdc\x0f\xb9\xf9M\x8f\xbdj:`\xd6\x8c\x0e\xd5P\ +\xb9\x8b\xf2\x94\x88\xac44\xd1\xc7\xa5\x1aBx|c\ +\xfb)\x9f\xae\x98\xbc\x06\xc58^M1\xf8\xf8\x86\xb6\ +j>Y-n\x87\xaa\x85\x93\x1frL\x89\xc4\xd5\xd4\ +B\x8fol\x17\xe80\xc5D\xc4\x83\x98\xa6\xe4\x98\x0f\ +Z\x00N\xb3\x94\x8e\xe3\xbb\xc8)%\x9d\xa7d>\x1b\ +H\x1f<|p\xfc`\x87\x92_P\x04\x89\xcfQ:\ +\xd6c;\x96)\xa5\x0b\xd2\x04\xa2\xce\x16A\x0f\xbb\xe7\ +4\xb8\xe7|\x9a`@\x9a\x0f\xee\xf5\x9cR\x9a\xd7\xb8\ +\xe0\x05\xa9\xcb\xbb\xa7M)\xcdk\x5c\xcds\x96\xd2\xb7\ +\xf5\x9cR\x9a\xd38\x1e\xe0c\x993\xeeq\xff\xc5\xfa\ +\xb2\x17lR\xbf\x1aP?M0n\xd8T\x8b}\xf3\ +\x8d\xe0\x0e\xf9\xe1\x88\xb9E\x00\x18/\xd0\x1axB\xe0\ +\xd1\xf7\xdb(f\x03\x8b\xf0\x0e\x0dn\x9a\xb6\x86\xd2\xbb\ +c\xf1C\x1a\xd2\x16T\x80\xb1v@\xb4P/@\x1b\ +`\xdf\x96\xa0,2\x88\xde\x92e\x11\xd6\x1c\x1dX:\ +:\xd5\xcf\x90\x85\xb1Pa\x85(-B[0O\x01\ +\x86\x02\x8aR\xdde\x884\xc3\xfc\x88\xee\x1c\xc0\xbfy\ +\xd3\xaeEj$\x08\xf5\xa7\xee\xaf\x7f<`\xf7\x08\xfe\ +ai<\x9c\xd8?\xbcfw\xba\xbd\xde\xea\xda\xf7\x83\ +\xd1\x82f\xe6\xeb\xf3\x1eX\x1d\x85\x17w([j0\ +s\x7f\x9e<\x13*\xcb\x9a\xcc\xb8kt\xfalY\x0b\ +\xcc.v=]=j\xc2\xfb\xc9\xbc\xfc\xd2kbj\ +N_\x0c\x16\x92,\x87\xea\x8b\x04oO\xec\xaeF\x02\ +`\x5c 7\x05a\xb6\x9a\xba\x11%l&l\xd4\xa1\ +edN\x03\xe4\x1a6\xd8\x9d\xc36\x14\xda\x08\x87\xdb\ +\xc4d\x00:\xbc\x07 2\x8a:\x124.\xc3\xf36\ +P)\xc0!\x0b \xc4\xbe\x99\xd4\xdb%\xd0V\x0b\xbd\ +\xb8\x18\xda\x88\xba\x9f\xe3\x11-c\x9b\xf8\x9ci\xca\xc1\ +38z\xacl\xebM\x1b\x97\x08\xf5\x07\xa3\x1b\x7f\xd5\ +F\xc4\x96\x99=\xe2\xc50\x94v\x90\x8e\x8e\xab\xed\xb0\ +ik\x94\xa8C\x0b\x9bG\x16\xa2akD\xb0\x1c+\ +\xa9%\xca\xc2c\xa5\xa8\xe4F\x91\xd5&\xc4j\xe9\xc2\ +{\xb2\x9b\xd7\x83\x8e\x92K\xc1\xba\xcb\xde\xaa\xbd\xeb\x85\ +e5\x83\x91LKg\x22\xd7\xdb\x1f\xa7\xb0\x19\x9di\ +n\xeb,+\x85\xe3\xc2[fh\x19O\x13\x83 Y\ +#\xd1\x80\x90\xdd\x17\xd1\x84\x93\xd9\xb6\x91\x0c\x18D\xf1\ +\x96.@\xb1@\x88\xc6\x8a\x14\x1d\xa9\x09\x18\xc9\xa4E\ +\x1dY\x5c;(Kz\xec\x90Tr\xc5\x0dR\x8a\xb1\ +\x16\x89.\xde\x832\xcc\x13J\xa2\xe2\x80T[\x00\xcd\ +\xa8oSjra\x1a\xec =\x86H\xcc\xa5u\xa8\ +!\x89k~ j\xd2\x0b\xd24\xb2\x9a\x9aH\xb7\x02\ +\xbc\x1a&J\xce\x9b&\x87\x83\xea@\xc3\xccc\x91\x0d\ +P\xb2\xb4\xb4F\xa2\x89sZ\x14@\x94\xcc\x1d\x02\xcc\ +\xc8t\x14\xd9,\x12bD\xbe\x9f*\xfb\x80\x19\xa7\xdb\ +;Q\xa47\x80\x17\x93hv\xd9\x9b\xfa\xeb\xec\x87\xe9\ +\x8aX$w\x17T\x87X\x8f\ +t\xb8T\xa3\x7fe\xd5WS\x8cu\xc7\x91G\xdd_\ +i\xb0\xc8USch\x0e\xfe4\x1dx\xd0\xca4D\ +XA\xfbC.~=\xaam\x9a\xf2\x89\x12\xcbQ>\ +VT\x8f,\xa0\xf7ao\x22\xb2H\xe9E\x95A\xd4\ +H\xef\x11+Q\xfbln\x83\xa8%\x0ch\xddQ\xb2\ +\x11b\xc9V\xe4\xae \xaa\xf3\x15E\xe5CD5\xe5\ +\x02X\xbcD\x0d\xf0Ej/\xaa\x968%\xfc\x80\xd4\ +\xff\xab\xd1\xbf\xb2\xbe\xafD\xad;\xfaY\x15\x1ffU\ +\xf9\x0a\xa2\x06\x5cQT\x9d\x11u\x87\x85\xe3] \xa1\ +\x0dqG\x8f\xb2\xa6\xac.A\xc0\x0e\xcch\xba\xcb\xf0\ +\xae\xe2v\x81\x8cF\xbd\xadG\xactSq\xce:p\ +w\xdcis\x8f&\x10pW\x15\x17\x88\xef\x83\xf8w\ +_\xec\xad`Yp;\xba\xbb-\x08*>\xd7\xdf\xbd\ +\x01\xe7\xcd\xe9\x9b\xe5\x10\x5c\xce\xde\xbdZ\xde[\xfe\xbc\ +\xec\x92H}\xf0\xf3\xec\xf4\x9f\xcbM\x0d34\x87w\ +\xb1\xef\xe1O\xbf\xae\x81\xe2\xae{\xf7\xf3\xde\x93w\xe7\ +\xe7\xdb\xd8?\xba\xdf\x95\xea~_\xaa+nV\xe84\ +t\x15\xf7\x93\x82\xbc\xd6\xfes\xa1\x8c\xfa\xbc\xe0\x0b\xe4\ +\xfa\xab\xd0\x99\xfag\xa8\xf7\xae\xbb>\xfe\xf7]g\x8c\ +\xc6j\xaa\xa3+\xefv\xf9\xbfME|\x03\xcb\x89\xcf\ +\xb4\xfc\xba\xbc\x98as\xbd\xfd\xc7\xaa\xd9\xa7f\x9b7\ +\xa12\xdf\xad\xff\xfe\xac\xe0\xbf\xa8\x0a~\x9c\x7f\xc3\xcb\ +\xe7\xbf\x1e\x9b\x02~\xcc\x81\xc6\xb7>\xbe\xa0\x1f\xed\xe7\ +\xc0\x02\x8a\xcd\xb3w\xc0]\xc6\xf5\xd6\x97\xb9\x1c(M\ +]]\xd8\xc0\xbd\xc2\xfa\xad\xcf\xb1 \xb8qay\xaf\ +\xb0y\xebK\x5c\x12\xcc\x0b{\x81\x80\xbaO@\xc7[\ +\xbf\xcb\x85\xc0\xa4\x8e\xff\xfc\x95\xf9e\x0b\x87\xa9\xcey\ +\x87\xfb\xb1\x93\x0c\xa8\xae\x1f\x08\x916&3\xb3E\xc5\ +\xcfI\xb3\xc8~\x09\xab\x91\xaf\xb6\xde\x1c\xe8?%\xba\ +\xf3\xe5\x90W'O\x96\xafV\x9f\x1c=\xaa\xbe}u\ +\xd5\x9d2*\xa8g\xd5\x0f\xdcx\xd1\x07\xec\xa8\xe5\xea\ +\x10\xe9\xf7\xdf\x86\xfdMU\x8aV\xb2\xad\xf2\x0b\x9b\xa3\ +\x98\x84,\xef\xd0d\xe7\x86X\xf4\xb2\xc0\x9f\xa0dL\ +\xf8\xf5T\x9d\xa3\xc0\x17h\xd78v\xb5[\x14M3\ +\xd3>J\xc7g\xf5\xd1\xc85\xda+\xfc\xd5\xcb\xee\xcf\ +=Yc\xcfN\xba\xcf:\x9f\x9d\x9d\xbc\x1fX\x1b\xd0\ +\x8b\xabYe\xbe=\xf5\xef\xf0\x16U\x80\xd0\x10\x9a\x00\ +R\xd2\xbbf\x03\xb2d\xad\xd8\x8cn\x08X\x183\xa7\ +\x0f\xf5}5\xc3\xb5\xac\xb0Z\x06\xd5\xeah\x904U\ +@\x91\x85a3\x16\xa4\xec\xb0\x85Q\xf3\x100\xee\x1b\ +\x15$\xea\xaf6BM\xf6\xed\xbb\xac\xb1i8\xae\xe8\ +zC\xaaQ\x861Q\xab9\xa4\x0c\x07H\xe3\x0e\xb3\ +\x06aX\xb9\xb31\x93\xb0\xf7Xy\x8eV\xd9\xe1\x18\ +>\xdc\xaa\xc9=\x8f\x89\x0dA\xc1\xeaBiINZ\ +E-\x00\x98P\x87qS\x0e\xc2\xa3\xacF\xe7h\xf5\ +\xa2\x88#\xe9\xd0\xf0h`C\xa3\xb7P\x1b\x84\xaf\xd1\ +(\x13\xd8\x17\xae-\x91\xc3K\xa7f\xae\xc6\x059\xa7\ ++\x17\xf3\x14H\xfd\x8d\x026\x08TiO\x92\xb9J\ +-pr\x89Z\x9ef&k\xd4\x80b\xe8U\x0e\xc8\ +\x22\xa0\xf4Qu\x1d7\x1b\xe6\x80e \xb40\x19\xfa\ +HV\xecVC\xaa\xfc\x1c\x89I\xf6\xf2\x81o\x06\xb5\ +\x85B\xaf\x87m\xe6\x14\xd7\x0a\xdb\x08\xb1P\xea\xf5*\ +6\x8aZ\xd8\xa0\xff\x84\xd6\xdf\xb5Q\x93dK\xf6U\ +\x83\xb3Y\xac\x1bX\xb1}P\xb9\xa0/j\x16\xd0Q\ +\xa9\xa6F5\x8c:\x8c[x\x04a\xd1\xf5\x9e#\xe4\ +a\xf6\x0b\x01\xe0p\xee\x8d\xd2\x11\xa3&?\xd5\x99\xca\ + Z\x9a\x91\xf3h\xdb{\xf6\x89X17\xe0\xf9%\ +'\xb1v-tv\xaeS.\x1a\xe4\xd0q\x07\x1b\x22\ +\x06\x92\xdc>4\xf9\x12\x1b\xdc\xfe\xe8XR\xde=\x0d\ +$\xf1/\x09 \xd3@QN\xe3 Y\xe5zH\xaa\ +\xac\xd6\x0e\xa8\xa8\xde\x17\xfa\xc8i\x89e\x19u\xa1\x16\ +\xc6\x22\x8e\xa9\xbdk\xba(vP\x92+A\xbf'\xe0\ +\xe5\x10\x84}\xf40\x16\xc0\xb2S\xb4f\x04\x99T\x01\ +D\x14\xdd\xec\x08u\x08\xe7\x5cq\xc4\x12$'\x98\x94\ +\x1d1y\xdd=\xa2\xda\x18\x01\xd5\xb7)ZS\x89\x08\ +\xdf\x1e\xdb\x1b\x9b\x19\x97en\xd8D\xcc\x06\xce\xc8\x83\ +S{\x04K.\x90\xa0!\x84D)\xc3!\x01{\x88\ +\xd2@\xfa\xd5\xad (\xf77\xab\x07\x944)\xc5\x0f\ +B\xa1\xd1\xc2\xa0\x0a`k@.\xa4\x1dV\x83s\xac\ +03\xf3>P9X\xd4jmDK\xd1\x09\x046\ +AC\x9b \x80\xc4\x04\xf5l(\xe1\xa8\x13T\xa2\x85\ +brN\xd1\x8au\x8cN\x13\x94\xbd\x05\x82\x88OP\ +\xa4F\xc9AYr\x15\xeahT\xee\x86\xe4lz\x94\ +\xda\x14\x1c4\x16X\xfe\x19\xa1Z\xf2kbT\x81\xdb\ +0-Hu\xd8107G+\x13\x09\xf14\xaah\ + \x22fe_\x12F\x94\xfbL\xee\xb7\xa3\x1f*B\ +\x19\xf41\x82\xb0\x91\x04A\xc94\xa2BMTSK\ +&\x8b\x96\x92\x01\xb2\x85\x1e\x17J\xe9\x06\xba\x85\x9a7\ +wR\x89\xc2\x92\x8d\x93Gl\xa2\xa7\x11\x1d\xf5\x7f\xbc\ +\x8dZ\x03\xb0\xa0\x18\xc7)L\xc2\xc9|\xe4h\xc4\xb6\ +\xb9\x9fA\x11\xac\xb9\x92\xf4\x097\x1a\x91\xa0\xe0\x96\xe5\ +\x7f\xbfO+\xfb\xc2\x1e\x03]\xbe.\x0a\xfdz\xf6\x0d\ +46\xfa\x92C\xd7|\xedc\x81\x97\xd4>\xae\xa99\ +\xd4>\x12\xe6\xa5R\x06\x02\x95\xc2(8%\xaa\x0e1\ +5\xd2\x82\x5cWz'-\xa5{\xac2_%\xa0\x9e\ +\xb0V\xd7PpD@jT\xa63\x0f\x8f,L,\ +I\xfbL\xe7I\xa8yC\x99\xce\x14o0\xd3\xcd\x17\ +\xcd\xf3\xc9O\xfeL~\x7f&\xbf\x9bI~!\xcd,\ +\xc5\xae7#\x16\xda\x81\x98\x7f\xe6\xc9\x7f\x93<\xa9\x90\ +c\xac\xdb\xed\xb4\xd0\x9d]2\x1c3\x89\xe3\x87\x99\xc4\ +\xe1\xc3Lb\xb9\x93I\x1cv3\x89\xebn&q\xfd\ +0\x93\xb8~\x98I\x5cv3\x89\xcb\x07\x99\xc4\xe9\xc3\ +L\xe28f\x92\xd9\x18\xcf\x17\xd6\x07\xf6\xbb\xaf\x0f.\ +\xcf\x93w\xc6D\x89\xd8\xdc\x16H\xd1\x22\x5c\xf8*\x8b\ +\xc18Lc\xd7\xbdO9\xda1I\x93\x95\xb7\x80s\ +y\x8b7\xab\xcd7\x17a\xce\x0d\x00\xd1H5\xb0\x03\ +\xa6\xb7\xecq%b\xe2\x02/|'\xe7\xf2\xba\xe2\x22\ +\x93s\xf8}\x9a\x9c\x13\xed\x94\xa4\x88\xadwx\xcd\xf5\ +6\xca\xf6\xe6Z\xacv\xc9pks-\xc6\xbd\xb5z\ +-\xfde\xd0?p\xdca\x1bo\xdd\xdaa\xcb\xed\x0d\ +6\xe4\x06\x8c\xd4!\x9b]\x1b\xd4\x86j\x05mv\x93\ +\xa6\xfc\xed\x0b\x9c\x0e\xfa)^\x94\xcdBh\xde\x8b.\ +0\x02\xfaR\xbch\xd7i\xe6\xdcj\xf4\xa2\x01\xf0h\ +\xae\x98\xa1k@\xb22\x14\x1a\xedz\xe2\xfe\xa5\x1d\xc1\ +\x9c\xd6\x0e\xde\xc0\xde\x1f\xa8\x5cn\xefh\xad\x01\x8a\xb3\ +\xfa\x15\xb57\xef\x1a\x9c6u\x8da\xdb\xb0\xf6:\xa9\ +\xdf\xe5\x9c\xee\xce\xc2\xf0Wb\xd5a{\x15\x94\xf2\xd1\ +\xa6\x09-\x093E\x17w\xb21\xd8P\x05\xb6L\x12\ +\xb2\xbc=_\x1bD\xea'\xf2}Csg\x9fa\xee\ +>]\xd1\x87gS\x8f/9\x0e(c\x09h\xdc\x12\ +\xd9\xb8\xf6\xc1\x19\x1b\x93*\xf5\x8b9w\x12\xa7#\x8e\ +\xa6\x12\xea\xd1\x87pN\x009\x12k\xa0\x89\xe6\x1b\x8c\ +\xab\x9e\xc6\xa6NA\xb8\x85\xba\xb6d \xcc\x1a\x87\x0c\ +3t`F\xb3%\x84\x13_-\xc2D\xfa\xe1\xf3\x11\ +\xf0\xc5\xceGXs\xac\x9cG\xd0\xd8\x83p\x1b\x93h\ +!\xe2\x93\xeb4\x1b\x1bR\xe4P\x8a\x130\xc1\xb0.\ +\xb7\x00\x8f\x9a\x0c0T\xebS\xa6\x81\x22\xf3\x91hs\ +\xa3D\x1f\xb1\x9a\xf4h\xc6(l[(W\x0eL\xd7\ +\x1e\xc3 \xef\x98$m\x14\xe1Z3\xc9\xa9\x80\xf2\x07\ +\xb0\xa3D\xb8\x82\x1d\xd1\xef\xcc\xaf)\x1a\x12e`_\ +?\x99\x1by\xcdQ\x88\x93da\x96AaG\xac\xcd\ +\xd4\xb5\x90$\x02\x8b\x9aKjh)\xe2\x85:\x889\ +\x17=W\xa0,\x885\x89\xcb8\xa4\x0f\xa5\xfd\xb8B\ +\x22\xb1Z\xe1z\x14Je\x8e\xe9\xa0\x13T\xb29$\ +jNP\x83\x06J\x1e}\x01IM\x18\xb2\x0c\x11\x1b\ +9\x93\xe2\x91{\x0b\xc2@*sgB3\xe9\x17\x90\ +\x988\xc8\x8d\xac\x0e\xabO\x88\xa0B\x9a\xd4\x95\x14\x0a\ +)G\xc1\x0dB\x98\xa2\xa7\x08FAW\xb0\xa4\xa3\x1f\ +\xf6+\xcb\x9b\x00)p\x99.\x90\xa8\xea\x11gC'\ +\xd5,\x0c\x01\x1d\xfdH\xa0\xa5:\x04\xf7\x98\x92Z\xef\ +6\xd9\xaa\x97\xa2PH\x87\xa4\x81\x22*Y\x8f1\xb8\ +\xe9\x9e\x91\xf7Zvn>\xcbQ\xff\xea\x87`\xbe\xf9\ +\x7f\xaf\xd7Z\xe4TM\x01\x00\ +\x00\x00\x17\x91\ +\x1f\ +\x8b\x08\x00\x00\x00\x00\x00\x00\x00\xed=io\xe3F\x96\ +\xdf\xfbWp\x9d/i\xacH\xd5}\xb8\x8f\x01f\x82\ +\x04\x016X`&\xc1\xcc\xb7\x80\x96([\x1bY2\ +$\xb9-\xf7\xaf\xdf\xf7\x8aW\x91,Q\x94,\xbb\x03\ +\xc7v:\xa6\xaa^]\xaf\xde\xcd\xaa\xa7\x8f\x7f\xdb\xdd\ +.\xa2/\xd9z3_-?]\xd0\x84\x5cD\xd9r\ +\xb2\x9a\xce\x97\xd7\x9f.~\xfb\xf5\xc7\xd8\x5cD\x9bm\ +\xba\x9c\xa6\x8b\xd52\xfbt\xb1\x5c]\xfc\xed\xf3\xbb\x8f\ +\xff\x15\xc7\xd1?\xd6Y\xba\xcd\xa6\xd1\xc3|{\x13\xfd\ +\xbc\xfcc3I\xef\xb2\xe8\xfb\x9b\xed\xf6\xeer<~\ +xxH\xe6Ea\xb2Z_\x8f\xdfGq\xfc\xf9\xdd\ +\xbb\x8f\x9b/\xd7\xef\xa2(\x82q\x97\x9b\xcb\xe9\xe4\xd3\ +E\xd1\xe0\xee~\xbdp\x80\xd3\xc98[d\xb7\xd9r\ +\xbb\x19\xd3\x84\x8e/j\xf0I\x0d>\xc1\xd1\xe7_\xb2\ +\xc9\xea\xf6v\xb5\xdc\xb8\x96\xcb\xcdw\x1e\xf0z:\xab\ +\xa0q6\x0f\xdc\x01Qk\xed\x98\xb01c1@\xc4\ +\x9b\xc7\xe56\xdd\xc5\xcd\xa60\xc7PSF\x08\x19C\ +]\x0d9\x0c\xear\xb7\x00T\xec\x9d\x8c\xab\xf5G\x07\ +\xf4\xdf\xc1\xbf\xaaAY\x90lV\xf7\xebI6\x83\x96\ +Y\xb2\xcc\xb6\xe3\x1f~\xfd\xa1\xaa\x8cI2\xddN\xbd\ +nJ\xec7\xc6ml\xc92\xbd\xcd6w\xe9$\xdb\ +\x8c\xcbr\xd7\xfea>\xdd\xde\x0050\xe3>\xded\ +\xf3\xeb\x9bm\xfdy>\xfdt\x01\xeb\xe3\x82\xe4\x9f\xcb\ +\x19\x5cVtD\x12\xcer\xd0\xa2[\xbfJ\x98\x84F\ +k\xab\x15q \x0d\xe2kt7]Mp\x86\x9f.\ +\xbe\xaeV\xb7\xf1\xea~\x9b\xc0\xa8_\x9b\xfdB\xe9\xdd\ +\xfd\xf6\xf7l\xb7\xcd\x96y7\xb00o\x95\xae\xda\xb5\ +K\x1aK\xac:\xc8vw\xab\xf56\x9e\xcd\x17Y>\ +\xd8\xf8fu\x9b\x8d\xef\xe6KX\xf6z\x05\x0f\x93\xcd\ +x\xb5{\xbc\xce\x96\xf1|\x02\x946\x86v\x8b\xf4j\ +\x91\x8d\xd3\xc9v\xee\x0an\xd3\xc5b,\xccN\x98q\ +5\xd5\xbb\xe5up\xa0\xdd\xf4\x0e6\x96r\x19\xac}\ +\xacj?C\xf5\xc7i6\xdb X\x8es\xfc\xc4\x05\ +%\xae\x0ej\x81l\xb2t\xfd\xd3:\x9d\xce\x81Yr\ +8\xaf\xcb\xc9j\xb1\xc8&\xb0o\xe9\xe2!}\xdc\x5c\ +T\x00\xd0U\xb3)\xb7T\x15\x9dB\xb7\x9b\xed\xea\xae\ +\x84\x85\xdd\xd8>.\x00+X\x18C\x8f\xab\xf5\xe5w\ +\xf4\x8aI\xce?\xb8\xa2\x15\x90\xcf|\xfbxI?\x5c\ +\xd4mV\xb3\xd9&\x83\x81\x89W\xe6\x88\x06Z\xc0X\ + R\xc6O\x1b\x8d\x84F\xa3\xc1\xd1\x18\xa9F\xfb8\ +n.\xfbiht\x5c{y\xb3\xce@\xca|\xf7\x9f\ +_\xfe\xe7\xe7\x1f~\xb7\xbf\xc7\xaa\x07\xcd\xccP#\xaa\ +\xfa\xeb\xa2\xf4\xb7\xe5|\x0br\xe4~\x93\xad\xff\x85\xbc\ +\xf8\xbf\xcb\xdf6Y\x07\xea\xd7u\xba\xdc\x00\xe3\xdf~\ +\xba\xd8\xe2\xe3\x02D\xef\xf7\xcc&Vq%\xed(\x06\ +\x92I\x94b\xf2}=?\x0a(\x11\x168\xd1\xaa\x1a\ +5\x8fPjd\xc2\xb9\x10\xa4\x9e\xcb\x8e\x01\xac\x14\xd8\ +\x83\xf1`\x99\x0f[aq\x0f\xbe`\xb9\x01,\x0c[\ +%\xce\xd5\x8a\x84\x0bn\x08o\xcc\x95\x12\x06\xc5B7\ +\xa6jT\x22\xb9\x94\xd66\xa6\x0a\x1dHD\x86\xd9G\ +\xca!\xb2\x0c\x10\x9c\x9c\xe0o\x0fy\x97\xc4\xa5X,\ +\xf7\x93r\x09\xa5\x85f\xf1\x81!\xb3\xd9\x8cd\xb3!\ +\x1c\x95p\xca\xadPz\xd8\xc0$\xa6\xfd\x03\xa7iz\ +\x95\xdaA\x03k\x06RB)\xbb\x7f\xe0\x10#\x86\xf0\ +\x0b['\xe9\x10\xfc\x0a\x0f\xbf\x7f\x06\xe6=\x96\xac\x03\ +\xcc{\x9bn\xd7\xf3\xdd\xf74\xb1\xf8c\xe8\x88\xc0\xaf\ +\xca?Y\x09|\xccXB8cz\x14+N\x81!\ +-\xb3/\xc0\xd38\xd1t\xf1,hlv\x8dh4\ +gC#\xe0\xca\xfd\x18\x87\xc6\xe2\x13\x05,\x0a\x05\xeb\ +\xe6\x14\x1f\xa9\x86\xd5r]#q\xb2C$\x22\x00\xf3\ +$\xc8\xe4\x11\x0d\x1c\x82]\xd0\xbat\x16\x84\x9d\x05a\ +\xd7\x9f.\x10\xb1`\xf9\x1c\x12\x95G\xa1\xb5\xd9\x87\x16\ +\x96\xf5S\xa9%OG\xef\x1e\x15c\x90\xf0(o\xd3\ +\xa3N(Umj\xd4Fs\xd3\xa6F\x93hi\x0c\ +oP#\x85!\x18\xb7\x034\xcc\xf1\x86\x8d\xc3\xd6p\ +Sc\xe6~N4l`,q\x94a\x13\x1am\xb0\ +a\x03\xa3\xa9C\xb2\xf1\x1cL\xdd\xc2'\xa5\xa6\x9f\xb9\ +=\xea\x1cF}\xc8\x8dF'\xb2\xc1\x88`\xcc\x18-\ +\x89'\xcd\x90\x11\x8dM\x8c\xa0\x86\xcb\x06#\x828\x94\ +Z[\xdb`D\x0a\xa0\x9a1\xd2\x9dMW\x88\x10\x90\ +\xbeT\x12.\xc5\x08|(\xca\x09\x98\x1b\x84\x8f\x8aG\ +\x94-\x08 \xa4\x02i\xc2\x13\xcb\x15e\x9c\x8f(\x05\ +\x8dH@\xb6\xbe\x1fb\x19\x05\xd08\x9cP\xa6|6\ +\x19D(A\xb2\xa4\x9e\x05\xbc\xdfV(}\xbbV\x87\ +\x89\x94 E\x89\xc7\xc8\xc1\x19^\xa539k\x99L\ +$a\x80G`w\xf6a\xd0\xf8*8\xbe\x96R1\ +\xea\xedcp|\xc3\xafdv\xd5\x1e_X\xb0&-\ +\xeb\x1b?h\x9e\xe8\xd9D\xb5\xfb\xd2F\x08\xc3\xb4\x19\ +\xcc\x9f\xa0\xc8\xbf\x01\x7f\xc2\x0c{\xf8S3o#\x9d\ +\x1a\xd4 \xbbA\xb3\xd1\x06\xf7\x09\x96(\xa9%k0\ +_\x17t\x16\x02\x05\xde\xe3,\x91\x94i\xd5uuB\ +v\x90\x06\xfa\xe29\xbbY\x0e\x8c!\xb4\xe3B\x0b\x9e\ +\x00\xb0\xdc\xc8\x01X\xc58\x9aF\x14\x14\x856\xf0\xa8\ +$\x10&h\xf6\xf7\x03\x85\xcd3h\x16\xc4\xf4p\xa2\ +\xca\xcd\x94S5\x0b3=,\x9c\x0bKBR\x036\x86>\x84\ +)!\x19\x89i\x98\xb4\x0f\x90U\x96\xc1>?\x89\x8b\ +\x05\xfe\x0e\xe1b\x91\xbfS\x08i\x02i\x15\x8a!~\ +x\x9b`\xa9,\xb6q\xe8M\xe3\x81\xad\x9a\xa9\x99\x9c\ +\xc9\xf3\xd0\xf3S\xecFK\xfb\xce\x08\xe1\xcb\xc6\x86\xa5\ +\xa68*\x0b?t\xefB|x\x22E*\xaf\x14M\ +\xb5\x00,\xcbc&j\xf0\x89\x99g\x8c\xa1\x1c\x5c\xba\ +\x17\xb1\x1c\x1eX|>\x04\xeds\xec\xf0\xb5\xb0;z\ +%\xd0\xcb3\x18_1D\x1f~\x89\xff\xac\xa8\xb3G\ +\xa2\xae\xef0\x03\xe3D+ \xc4\xf6jc)\x13j\ +(\xd4\x8e\x18b\x12\x96m\xde?\xd3f|Kd\x0a\ +\xfe\x5c\xc8\xa4\xc5i\xb3\x06.1l\x07\x980\xf4U\ +\xe2R\xbf(.\xe3\x22\xe0I^%2%}v.\ +\xe7\x0dlZ\x89\xe7J\xa9}}\xc8\x14D\x9e\xf2\xc6\ +\xed\x95,\xfd\x8c\xc7\x90{U\xe2+D\xdd\xf9\xc2\xc4\ +\x7fuE+\x88:\xc3A\xe37E[\xe0\xf2|o\ +t\xff\xea\x8a\x16\x90y\xbe\xc0\xfd\x9b\xa2Uo\xda\xe6\ +d\xd4\xbdi\x9b\xf3!S\xbfi\x9b\xf3\xe1\xf2M\xdb\ +\x9c\x11\x99o\xda\xe6\x8c\xc8|\xb6\x80\x83\x87L\xa1\x12\ +*\x99\xb2|\xe4.B\xbeN\x1e7/ /kD\ +\xc6\x94\xe3\xcb\xce\xd7\xc9\xe1\xe6\x05\x22\x8a\x1e*\xad\x82\ +RA({\x95\xb8<\x9f9\xb9\xdf&\xf2\x90\xa9\x11\ +?\x8a\xda\xd7\x89\xcc\xf3\xbd7\xe8\xd8\xe61\x1e\x1d\x02\ +\xe98\x8a\x0d\xae\x14P\xc8_#\x0a\xcfx\x94\x9a$\ +\xdc\x08E%7m\x5c\xeaDZ\xa6\xa5\x10#\x87\x1e\ +\xf2:5\xce\x19\xcf`\x0eAeI\x97\xe65\x1aB\ +\x94\x9c\xd3\xaa\xdc\x87Lf\x12\xcb$\xfc\xbcf\xba\xa4\ +\xe4\x9c*\xe70*_9]>\xff\xab\xea\xbf\x8c\xb7\ +C\xe99\x95\xcf_\xdc\x0f\xa7\xf4\xd8\xf3'o\x01\xa2\ +\xfd\xb8|\x8b\x5c\x9e\x0f\x99\xec|\xef\xfdi\xd2\xc5\xa2\ +I$\xe7\x94Z\xf3\xaa\x83\xe9\x94\xfde_\xf8S~\ +,\x01\xbd\xd8\xc9\xba:O\x14\xe8\x16w\xbfiD\xbe\ +\xe5Q:\xc0\xd5\xf9b5!f\xa3\x0c\xd9\x8dP\xf6\ +R\xcc\xf6q\x8cY?\xddS\x95\x17\x15\xf3\x94N\xbf\ +\xcc\xb3\x87w\x15\x1a\xae\xd2jIw\xe9u\xe6N\xcd\ +\x02\xf2\xf2\x1b\x1eE\xc5\xd5j=\xcd\xd6e\x95r?\ +\x8d\xaa\xe2`m\x9d\x89\xd5\xdb+\xec\xb5\xaa'\xe1\xfa\ +\xcdM:]=|\xba`\xedJL\x8a\x0a\x0bk\x17\ +\xe3\xd5\x0b\xa1\x13\x0b\x9ec\xa7\x09^\xc0\xd0\x98F\x16\ +\xdc=\xda\xae\x9c\xae&\xf7\x98\x1f8\xbe\xcf\xb7\xf6n\ +\xd7i~\xbf^#\xc0\x22}\xcc\xd6\xcdl\xb5u>\ +[c\xaa\x86E\xc6[\xafds\xb3z\xc8\xd1\x82d\ +~\x9f\xb5G\xc0z\x7f\xd1\x1e\x0cV]\xafq[B\ +\x0d\x1f\xe6Kh\x10\x979v\xa9\xec\xac\xbd\x80(\xa7\ +\xa9M\x07s\x05\x04\xe0O\x8a}\xcd\x1f\xf1^\xbe?\ +\xa3\xfb\xf94\xdb\x84\xe7\xe4\xea\xe2\xab\xab\xd5.\x5c\xbf\ +\xba\xfa?`\xd4\xf8.\xdd\xde@\x0f\xb3t\xb1\xd9\x07\ +\xb2\x5c\xb9A|\x90\xbcf\xbbZd\xc0b\x93\xac>\ +\xeb\x8d8\xf2\x8bM\x83\x1a\x8b\xad\x0b\xe2~\x99\xde\xc5\ +\xd7\x8b\xd5U\xba\xe8\xc5\xf1m\xba\x9b\xdf\xce\xbff\xd3\ +\xfa(}\xb3\x0fo\xc5\x85\xc8\xaaQ\x02\x93+\xd9r\ +\xfb\x88Y\x94w\x8fX\xd6\x90>X\xc0\x84\xa8-O\ +\xcc\xa6<_^\xef0\x83\x05\xf2WENU\xd5c\ +\xa0*\xbb\xbd+jk\xe6\x89\xa2/\xf3\xcd\xfc\x0a\x0f\ +\xc2{+\x04\xd8%\xe6\x1e\x9e\xb6Jq5\x05<\xce\ +\x09\xa5\xe2f\xb5\x5c<\x16`\xa5,\xe9\x8a\x10W~\ +\x9bm\xd3i\xbaMkyR\x96p\x90\xa8%j\xd6\ +\xd3\xd9\xe5?\x7f\xf8\xb1:\xe3?\x99\x5c\xfe{\xb5\xfe\ +\xa3\x9cB\x14!@z\xb5\xba\x07\x9a\xad.=`\x12\ +\xe3\xc9%\xca\xd5t\xfby~\x0b\x0c\x83\xa9\xb1\xff{\ +w\xbb\x00\xc9VU4\x80\x11\xdbu\xa7y\xb7\xeb,\ +O}\x1d\xcc\x16>\x9d\xdc\xce\xb1\xd1\xf8_\xdb\xf9b\ +\xf13\x0e\xe2\xddE(:\x9do\x17\xd9g7f\xfe\ +X\xaeb\x5c,\xa3\xbcI\xe0\xad\xf2\xe3\xb8D\x83\xfb\ +t]\xa3\xe7Z\xab\xeav\xe86\xa4\x0eM\xa2\xf16\ +'S\xa3\x18\xf3yf\xb1z_\xa2\xf1\xba\xa2\xaaP\ +xA\x08\xa6\x18z\x1a\xa0}\x0cxj\x98\x01\x8bY\ +\xc9a\xc0\x91\xab\x96V\x13\xcc\x13Jt\x02\xda\x87\x83\ +\xed,y\x22\x89\xf0#\xdc\xf9\x14%\xf1R\xe0\xac\x81\ +\x05k\xa4\x167,\xea\xf4fVSB\x85d\x1ff\ +\x80\xc1K\xc0\xed\xf7-\xc5\xebr\x17\xbfw\xd5\xde]\ +\x99\xcdv\xbd\xfa#\xbb\x5c\xae\x965)\x16\xf7\xf2`\ +<-\x94\x7f\x83\xa5\x90x\xad\x1b\x99\xb58\xc6$\x22\ +.\xd7\x97W\xe7rIX\xc0\x00o\xe4\x87\x03\xba\x8e\ +\xa5H\x80\xf5t\xa3\xab\x10J5\x81\xb5\x81\xa6\x1ey\ +\x8fq\xb8\x98\xf8\xd6K\x07i8(\xa8+\x85\xf1\x9f\ +\xf6\x14U\xa2\x98R*\xb0*\xeeR\xbd\xb2\xc6\xfd\xa4\ +R\xf6c2*\xc5;7\xa3s\xdc\xe9@\xce\xbd\xbe\ +\xddQ\x03w\xe7\xcc8B\x9d\xe0\xcd\xb3\x140\xb9\xc8\ +L\xd7\x93\xbeE\xe0\xec[\xa9a\x8f 1\x1cY\x0b\ +\xd3\xb8$U\x0e\xefn\xe7KD}\x83\x9c\xeaz\x97\ +\xc1\xd5\x09b\x13\xaa_\xef\x02\x84Z\xd7>\x06jQ\ +jFTj\x8c\x0b\x88Q\xd9y4\x8901\xad\x15\ +R\xc8\x08d\x017 \x18Fy\xe3(\xce\xffV\x1f\ +s8`\xf4\xaa\xa6lQ\x17\x14\xb0\xb0\x0d\xd5\xbfE\ +TT\x92\xe8kh\xba\x9bm\xban\xdf&+\xeb\xb2\ +%\xcc\x9b'TP\x8c\xab\x9f\x8fL\xd0\x1d\xa0\x84\x80\ +C\x10\x03sh0\xe3\xa4\xed\xe1\xad\x93\x04\x92%G\ +\x0b$\x1b\x10Hn\xa3\xa4\xd9\xcb\xbb\xfe]\xdf^\x89\ +\x14\x10\x0e\xe7d\xb6\x8f\xe3\xeb\xe2\xc1\xe7\xb9\xee\x084\ +\x91\xda\x12\xa6\xc0\x17\xc3\xc4\xba\x8c\x0b\xc5p\x80\xea\xb9\ +\x06\x88a%\x12s\x01\x80\xc2\xe1\x12<\x1b\xc6\xbc\x10\ +LN\xd1\xa0h\xb8flT\xe5du$Mq\x8b\ +$\xa5\x22\x8aMb@'\x19\xd0Te\xd2\xd5(.\ +\x1e\x1bEe\x0b$\xef\xaa|T67\x8d\xc2\xba\x1d\ +\x19\xd5-\xa3\x12\xd6\x07\x08\xb6\xf2\x07+\xe7R\x0d\xd4\ +\x9d\x5c\xcd6\x0d\x1e\xeff\x91m\x08\x88\x9e\xeap\x12\ +\xdb\x86xj$\xbd\xad\xe4\x99\x9fI\xf9\xb0\xbc\x04\xa5\ +?\x84\x03\x02R\xb9\x22\xaa\x1es\x04\xdc@%-\x11\ +.9\xa7\xa2`b(G\xa9\xd5s\x0d\x10S\x8e\xc9\ +9\x15\x03\xd3D\xa9\xc4p\xa2E\xc7 !\x0a\xd3\xff\ +\x0c\xd3\x1c\xb5_\xe1.\x88\xfa\xcc\x5c\xd6 \x1a\xee\xc1\ +\xd7\x88\xf1\x8e\xb9\x00S\x88\x89\x10\xd8j=\xbf\x9e/\ +\xd1S\xf8%\xa2\x86\xe2\x97\x0c\x80\xa0\xb8-\xda\x00\xb6r@\ +Z\xd7v\xe1\xbd\x11\xb0\x0c\xc7\x8f];\xa3@\xfcU\ +E^\xffnR\x96\x10\x85\x82\x1a ]\xe6\xa3\x1a \ +\xee\xb6\xf9\x1a\xdd\xe2\xbc\x12\xdc\xa6\x09lL\x82\xe9\x91\ +ARD\x0c\xb3t#,R\xa8\xa2\x04i\xba,*\ +\x1f\xb0e\xde\x02j\xdd\xd91Ex^\x8d\xa35\xe0\ +\xb1\xc0\x01s\x87\xb1\xbc(?pf\xa9\x22\xb4.*\ +\xa0q5\x0e\x9e[`\x0a\x04s_\xa7P\x8d\xed\x83\ +\xe7\x04s@a\xc30T\xa3\xd7\x95G\x03\xab\x8fT\ +b\xde2\xc9\xe8H%L\x1a\xe2\xbe\xaaa\xafi\x1c\ +\xea\xd9PM\x99\xa6\xb6\xe8\xb9\xfa\xc8\x81Z`i\xb0\ +\x070\x7fp#\xad\xd5\xef{h\x0b\x5cL\xcb+\xe2\ +\xb2`\xe6hG\x5c,A.B\xda\x92\xb0b.\xea\ +\xa2\xe2oNY\xd8@\xa9\xa8\x04\xab*\xdb\xc0^\xdf\ +X\x84\xe2\xc7\xd1\x954\xa0\x89\xb8WV\xf7^\xcc\x07\ +\xd4\x81\xa3+L\xe6m\xea\xfa\xb8\xdb$'+\x0e\xa8\ +\xc8\xe9\x0at\xa85\xd4\xd1\x15~y\x00\xd8\xd5 (\ +\xc0\xd91\xc5g\xf7\x7f\x5cF\x01\x88\xf4\x8b `p\ +\xba:\xd7O\x5c\xc3\xe6\x9f\x1d\xb4\x16\xd4\x11TQ\xea\ +\xd2-\x19\xc3\x0d\xc8,\xaf\xb0j\x84\x8b)\x9a\xf1(\ +\x07\xa5,\xaf\x97Q\x13\xbeIR\xb5\xafB\x02\x09\x1d\ +j\x93W\xe2\x11\x1a\xe0\x98\x0f{\x85\x95 \xbd\xfa>\ +$\xac\x04m\x0a\xab7\xad\xf8\xa6\x15\x036\x14&\xfa\ +\x07\xa5bI\xd0\x1b\x0e\x9bd{%\xdd!\xf1\xa4Y\ +G:I\xbeW6i\x16\x15 \xfd\x82\xc9\xf5\xda\x92\ +K\xd2*\xd1#\x97\x94\xe4\xaa\x90K\x82\xc8\xa3\xe5\x12\ +\x88\x98\x86XB\xfe\x0d\x89%0\x91K\xb1\x04*\xef\ +\x90X\x02\xe9\x16\x10K\x8ah\xd6+\x96$\xd5&\x17\ +K\x92\xcb\xa3\xc4\x12k\xf8\xbe\x87#Mvp\x1c\xf0\ +M\xd8\xbc\x09\x9b\xb0\xb0Q\x5c`\x16\xc43\x08\x9b\xa0\ +\xdb\xca)\xd8g\xbc<\xbcS|\x8ae\x82\x01 \xab\ +\xac\x0b\xab)M4\x97}f\x15lo%\xb5\x18\xb8\ +\xb4\xb4\xb0\xd8\x99c)\xea\xde\xde2\xc1\xbd\xb2\xf2!\ +\x97]\xd8\x06\x0c\x9f\x12\xb0\xae\xed\xc2{#`Yn\ +\xdf\x82\xd5\xc7\xd1\xae\xca\x0b\xbc\xbe\x8b\x09\x19\xa9\x9d\xf1\ +\x0c\xc6\x9b\xb5\x1e@\xdcnQ\xda\xea\xa5\xa9.\x0dJ\ +.\x8eiM\x8b\x95\x08\xc1\x98TuQ\xf9\x90\x1b\xea\ +`\xe0Y+\xdcX\x02_\xb1\xbb\xea\x5cP\xfa\x0d\x5c\ +I\xde\xbd\xcdE|^\xe8\x16#@\xce\x00\x87\xd4e\ +e\x8b\xdc\x5cwmh\xe4\x00yQ\xeb&\xd0\x80w\ +%a!f\xfbm+j\xa1\xa9\x11\xfbm+60\ +\x96\xf2&\xd6\xde\xc4ZX\xaca\x0c\x97\x11\xdb\xf8\x92\ +\x9ds\x1aQV\xd5\x01\x04\xf0\xc2\x88.\xc4Q\x19?\ +\x00\xebM0U\x17\x15\x7fsY\xe4\x1a\xb8\xe8\x01u\ +\x01\xbb\xb2\xb2\x0d\xec\xf5\x8dER\x15\xb6\x14\xb4\x82f\ +^Y\xdd{1\x1f!s\x1f\x8fJKM]\x1fw\ +\x9b\xe4\xe2\x88\xe5\xec\xef\x04\x12\xac\x90\xe9\xee6\xc9U,\xc9c\ +\x17N\xc5j\xed\x8e\xe0c\xf7\xc5b\xa4\x95\xda\xc8\xb2\ + \xff\x93\xabX\x84\xa5y`^\xe2k\x04\x8e\xf9_\ +K{\xbf\x00\x16\x95\xb9\xaf\xdd\xb7$\xb8:Q-\x06\ +\xfe*)\xfc\xc2\xaaM\xae`\xa1\x95\xb4*r\xa0\xf8\ +u\x9bE\xd7Q\xa3\xc1\xa0\xe8\xbc\xe4\x92\x19U\x9e\xd5\ +\xad>\x02E2%-\x87\x8dH\x84\xb2Bks\xe8\ +]z}\x19\xd0\xc4\ +Q\xc0\xaa\xc1\x0b\x8a\xbd\xca\xa0\xb3\x8c=cX\xd37\ +\x06\x1d\xa0pj\xba\xab\x96X.[$\x86\x83\x91\xc9\ +[Xj\xcb\xa5\xe37\xa4\x09\xee\xb8\xb1!\x16\xf7\x8d\ +TL\xccQ\x84\x86\xff\xbaK\xd0O\xd8\x92A\xd6C\ +\xc0\xa7\x18!\x07\xe5\xfe\xc4\xfb\xeer\x0f5x\xc2|\ +\xe5Q\xdb+\x0e`\xb1\xc1\x04l\xef\xdex\xdb\x17\xda\ +\xda\xb84\x17\xcc\xc1\xddm\x0ctpk\xabkS'\ +\xa1j\x88\xe9\xb5g\xa7\xe2\xa3\xf76>\xc3\xe6\x12{\ +\xdc\xe6\xdaa\x9b\x1b\xe0\xdd\xee\xfez{\x18\xdc\xe2\xfc\ +&js\x8b\xdb2v\x9f\xf4\xf6E*\xab\xf5_\xb9\ +\x12A*\xb5\xb0O\xe2\xb1\x135\xcfy\xb6w \xe7\ +J\x22\xf6o.\x19\xf4\xf2\xd0C\x89\x0a o\x80T\ +vN9\xf3\xa4\xb2D\x7f\xd6\x04\x18\xf7\x04\x0a%f\ +\xc8}\x8az\x11\x92\x9c&~\xca9W\x8b(V\xb5\ +G\xb5\xd4\xb5{z8Q\xb7\xc0\x02\xd8\xe9\xecL\xb4\ +>N\xdd?M\xb2\x94V\xc1\xc9\xd6\x19\x90\xa8\xe8g\ +\xd8\x1e\xeb,\xc6o\x8cT\xc6\x17(\xb5\xc4\xe8\x11(\ +\xc2\xef\xa4I\xbb\x076-h\xa0\xc1\x1a\xd4Sx\xf4\ +Tk\xa0W\xa4<\xb6\xc9q\xd7v\x09OZ*%\ +\xfdR\xb3w\xa9\x94\x0c\x89\xb0\x1d\x10\x86\x03\x85\xe70\ +\x02>0\xdd!w\x17=\xd4\x84T\xcf\x10\x9b\xb6\xa3\ +\x16k2=\x8by\xce\x8f1\xcf\xc1\x9a\xd7\x03\xd6\xd1\ +\xf6`v\x01\x8b,\xe0\x11\xf5;\x85\x87\x9c\xa9\xce\x08\ +\x87h\xb8\xe5Xxk\xb4O\xa0\x0b\xce\x8f#\xe3\xde\ +\x95v&\xa0\x8d\x0b^\x1c\xebp\xd7k\xab\xbe\xf1\xcc\ +\xd74\x9e\xe4l \xcd\x13\x0f\x1d\x05X\x8aW\x9c\xf3\ +G\xbc\x1f\xff\xf9\xdd\xff\x03\x0b\xb0\x0a\x0c\x99\x9b\x00\x00\ +\ +" + +qt_resource_name = b"\ +\x00\x05\ +\x00o\xa6S\ +\x00i\ +\x00c\x00o\x00n\x00s\ +\x00\x06\ +\x07\x03}\xc3\ +\x00i\ +\x00m\x00a\x00g\x00e\x00s\ +\x00\x16\ +\x02\x1b\xe1\x0a\ +\x00g\ +\x00o\x00-\x00n\x00e\x00x\x00t\x00-\x00v\x00i\x00e\x00w\x00-\x00p\x00a\x00g\x00e\ +\x00.\x00s\x00v\x00g\x00z\ +\x00\x11\ +\x04\xf3\xa4*\ +\x00g\ +\x00o\x00-\x00n\x00e\x00x\x00t\x00-\x00v\x00i\x00e\x00w\x00.\x00s\x00v\x00g\x00z\ +\ +\x00\x15\ +\x01\x09v*\ +\x00g\ +\x00o\x00-\x00p\x00r\x00e\x00v\x00i\x00o\x00u\x00s\x00-\x00v\x00i\x00e\x00w\x00.\ +\x00s\x00v\x00g\x00z\ +\x00\x0d\ +\x0e\xb9\xa6*\ +\x00z\ +\x00o\x00o\x00m\x00-\x00o\x00u\x00t\x00.\x00s\x00v\x00g\x00z\ +\x00\x12\ +\x0al\x90\xca\ +\x00d\ +\x00o\x00c\x00u\x00m\x00e\x00n\x00t\x00-\x00o\x00p\x00e\x00n\x00.\x00s\x00v\x00g\ +\x00z\ +\x00\x1a\ +\x01d\xbbJ\ +\x00g\ +\x00o\x00-\x00p\x00r\x00e\x00v\x00i\x00o\x00u\x00s\x00-\x00v\x00i\x00e\x00w\x00-\ +\x00p\x00a\x00g\x00e\x00.\x00s\x00v\x00g\x00z\ +\x00\x0c\ +\x009l\x8a\ +\x00z\ +\x00o\x00o\x00m\x00-\x00i\x00n\x00.\x00s\x00v\x00g\x00z\ +" + +qt_resource_struct = b"\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x10\x00\x02\x00\x00\x00\x07\x00\x00\x00\x03\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x010\x00\x00\x00\x00\x00\x01\x00\x00\xba\xe2\ +\x00\x00\x01\x81\x8a\xd9\xf0\x94\ +\x00\x00\x00|\x00\x00\x00\x00\x00\x01\x00\x00J'\ +\x00\x00\x01\x81\x8a\xd9\xf0\x94\ +\x00\x00\x00\xf6\x00\x00\x00\x00\x00\x01\x00\x00\x89\xa4\ +\x00\x00\x01\x81\x8a\xd9\xf0\x94\ +\x00\x00\x00\x22\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ +\x00\x00\x01\x81\x8a\xd9\xf0\x94\ +\x00\x00\x00T\x00\x00\x00\x00\x00\x01\x00\x001K\ +\x00\x00\x01\x81\x8a\xd9\xf0\x94\ +\x00\x00\x00\xcc\x00\x00\x00\x00\x00\x01\x00\x00x\xec\ +\x00\x00\x01\x81\x8a\xd9\xf0\x94\ +\x00\x00\x00\xac\x00\x00\x00\x00\x00\x01\x00\x00c\xbc\ +\x00\x00\x01\x81\x8a\xd9\xf0\x94\ +" + +def qInitResources(): + QtCore.qRegisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) + +def qCleanupResources(): + QtCore.qUnregisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) + +qInitResources() diff --git a/examples/pdfwidgets/pdfviewer/resources_rc.py b/examples/pdfwidgets/pdfviewer/resources_rc.py deleted file mode 100644 index 7e386e99..00000000 --- a/examples/pdfwidgets/pdfviewer/resources_rc.py +++ /dev/null @@ -1,3468 +0,0 @@ -# Resource object code (Python 3) -# Created by: object code -# Created by: The Resource Compiler for Qt version 6.4.0 -# WARNING! All changes made in this file will be lost! - -from PySide6 import QtCore - -qt_resource_data = b"\ -\x00\x001G\ -\x1f\ -\x8b\x08\x00\x00\x00\x00\x00\x00\x00\xec}\xebs\x1b\xc7\xb1\ -\xef\xe7\xe4\xaf\xe0U\xbeDU\xc0\xb0\xdf\x0f%\xce\xa9\ -\xc4\x8eS\xa9r\xea\xa4n\x1e\xf7\xdcO.\x88\x04%\ -\x1e\xd3\xa4\x0eI\xf9\x91\xbf\xfe\xee\x02$g!h\xb1\ -\x00\x16P$_s\x5c\xf2b\xb6\xb7{\xfa7==\ -=\xb33\xb3\xbf\xfd\x8f\x1f\xbe\xbd:\xf9n~{w\ -ys\xfd\xd93,\xf0\xecd~}vs~y\xfd\ -\xea\xb3g\xff\xf8\xfb\x97\xd3xvrw?\xbb>\x9f\ -]\xdd\x5c\xcf?{v}\xf3\xec?~\xf7\xcb\xdf\xfe\ -\xaf\xe9\xf4\xe4O\xf3\xeb\xf9\xed\xec\xfe\xe6\xf6\xc5\xc9\xef\ -\xcfo^\xceO\xfe|u\xf5\xf6\xee~\x91u\x82T\ -\xa0\xc0\xe4\xe4o\xff\xfc\xd3\xc9\x1f\x7fxss{\x7f\ -\xf2\xd7\xab\xb7\xaf\xa6\x7f\xbe>)\x8b\xcc\x7f.e\xbe\ -8\xb1\x02p\xf2\x87\xb7\x97W\xe7'\x8a\x22\xf1\xfc\xe4\ -d:mD\xdc}\xf7\xea\x97'''M\xf9\xae\xef\ -^\x5c\xdf}\xf6\xec\xf5\xfd\xfd\x9b\x17\xa7\xa7\xd7we\ -\xd6\x8a+g7\xdf\x9e\xfem\xf6\xdd\xfc\xcb\x9b\xdb\xff\ -3\x7fy\x8a\x05N\x9f\xd5'f\xef\x7f`Q\xd2F\ -\xfe?/\xe7\xdf\xcfo\xff\xf8\xc3\xfd\xfc\xba-\xc6\xdd\ -)\xaf>~\xb9\xe1\xf1\x8e\x9e\xa7\x08\xab\xcf\x9d\x9f=\ -=\xf8\xe6\xed\xedU\xb9\xb9}uz~v:\xbf\x9a\ -\x7f;\xbf\xbe\xbfkJ\x89]\xf2\xb3J~v;\x9f\ -\xdd_~7o\xc4|\xdb\x14\xa8}\xb2\x91\xfd\xab\x0e\ -\xf1\xed\xf9\xc5\x13\xf5\xf7\xdf\x7f_\xbe\xe7\x05\x11f\xe6\ -)\xd0)\xd1\xb4\xa1\x98\xde\xfdx}?\xfba\xba\xfa\ -h\x03\xe6\xfb\x1e%\x008m\xeeU\xca\xed\xa8^\xfc\ -puy\xfdMoa\x16w\xbb\xd2\x1b{zss\ -^1}\xcc(w7oo\xcf\xe6\x17\xcd\x93\xf3r\ -=\xbf?\xfd\xe2\xef_<\xdd\x9cB9\xbf?\xef\xb0\ -i\x98\xde\x9d\xcd\xde\xccW\xe4>f.\xf1\x9a};\ -\xbf{3;\x9b\xdf\x9d>\xe6\xb7\xcfw\x0d\x1c\x17\x19\ -\x97\xe7\x9f=\xfbj\xf6\xe3\xfc\xf6\xeb\xe5\xef\xef/\xcf\ -\xef_7\xb7)\x16?_\xcf/_\xbd\xbe\xaf\xbf\xbf\ -k\xac\xe5\x0f7?|\xf6\x0cN\xe0\x04)N\x1eo\ -\xdc4\x9c/\xaen\xbe\xff\xec\xd9w\x97w\x97/\xaf\ -\x96\xe2\xe6\xd7\xb3\xe6r\xfarv\xf6\xcd\xab\xdb\x9b\xb7\ -\xd7\x8d\xb0\xeb\xf9\xf7'\x9d\x87\x1f\xb5z\xd1\x96\xb6Q\ -\xe8\xcd\xed\xfcn~\xfb\xdd\xf2\xf1G\x00^<\x15\x1b\ -\x0a\xd3\xe2\xd6\xa3V\xdd[b\xabO\x9d\xdf\x9c58\ -4L_\xddL\xaf\xe7?\xdcO\xdf\xcc\x1at\x9b\xfa\ -\xfb\xd7\x1a\xdd\xcb\xd9]Cw\xfa\x8f\xbb\x86\xdb\xe9\xf9\ -\xec\xbb\xcb\xf3\xd3/\xe6w\xdf\xdc\xdf\xbc9\xbdk\xda\ -\xfc\xcb\x9b\x1fV\xa5\xde\xbc\xbd\x7f\xf3\xf6\xfe\xeby\xdb\ -l\x96\xe2\x1b\xd4k\x15,o/D=e\xae2\x98\ -/\x5c\xc1\xf4\xe2\xf2j\xbe,\xe3\xe9\xeb\x9bo\xe7\xa7\ -o.\xaf\x1b\xc8oo\x9a\x8b\xb3\xbb\xd3\x9b\x1f~|\ -5\xbf>m\x9e\xb8jq<\x9d\x9d\xdd/\x9a\xe8\x8a\ ->o\xae_\xbd\x97\xf5\x0f\xe7o\x1a;3/\xfa\xde\ -\xdb?\xd6\xdb\xbf\xfb\xed\xf9\xfc\xe2\xee\xd1\x1a\xdak\x84\ -6\xf7\xe9\x917\x0d*o\xe6gm\xa3\x5c\x01\xee\xfe\ -\xc7\xd6\x06W\xc9\xf8\xfc\x9d\xfay\xf3uc.S\x85\ -\x93\xc6\xc3A\xfb/\xae\x11\xfc\xd8\xd4^{\x03\x16\xf7\ -a\xed\xfe\xbf>{\xe6\xb0\xce`U\xec\xf4\xe6\xf6\xf2\ -\xd5eS\x13\xbc \x92J\xbaP\xab\xa3\x03!=;\ -9\xfd\xe9\xea\x87\xa6\x0b\xfd\xbe\x9d\xdd~3\xbf]\xe1\ -uw\x7fs\xf6MK\xfe\xfb\xdb\xdb\x9b\xef\xf1/\xf3\ -\xeb\xa5:\x0d\xef\xf9u\xd3\xcego\xefo\x16\x19\xb7\ -\xf3\x8b\xff\xdb\xb6)x\xfc\xf5_\xf5\xd7{\x9e\xbf\xbb\ -\xff\xf1\xaa\x81\xea\xd1\x0b\xbcxp\x02\xbfi\xac\xe8\xcd\ -\xec\xfeuK\xb2|\xae\xfd\xc5\xac\xfel\x99\xd5\xe4\xfc\ -\xe5\xa4\xed\x1f\xa1\xc0\xc9W'\xda\x5cMuq9E\ -*\xda\xc9^\xe4>\x91\xfe\xeb\xa4eP\x057\xed\xe8\ -jz\xfb\xf6\xaa1\xef\xef\xe6\xd77\xe7\xe7\xbfi\xfa\ -\xa5\x9bo\xe6/~\x05\x8b\xbf\x87\x9f\xd3\x85{{\x81\ -\x05\xde\xdc\xfff\x09\xcf\xb4\xe9\xd4o\xef_\x5c7\x9d\ -\xfao\x1ex6=\xda\xf5]\xe3\x8a\xbf\xfd\xecY\xdb\ -\xf4\xe6\xbf\x86\x22\xcfOno\xeeg\xf7\xf3_c\xc0\ -\xf3%\xc5\xd5\xe2'L\xe0\xf9\x02\xed\xd3%\xbf\xad`\ -_\xc8\x1c\x01|\xe5\xd0\x0b}\x1f\xf2\xf21\x22\xbf\x01\ -\xf8\xd1`\xffm4\xd8\x7f\xdb\x17l\x83O\x0bl\xea\ -\x82m{`\xfd\xd5H\x7f\xf2\xd5\xbe\xfe\x04?1\x7f\ -\x12\xbd\xfe\x84\x8a\xee\x03\xfch#\xffj_#\x97\xf8\ -\xb4\x8c<\x06\xe0>h\x5c\xb0\xe8\xd4e\xdf\xa0\x00)\ -\xea\xf3\x1bc\x82\x05\x8dP\xb1\xf6\xcf{\x03\x83\xf0\x85\ -\xa6\xcd\xa8d>\xbb\xfd\xd3\xed\xec\xbc5\x95\x15\xc6g\ -7WW\x0dqc>W\xdf\xcf~\xbc{\xe2\xb2\xfa\ -\x88!rc\x0e\x8d\x11\xbeY\xa9\xba6c\xda\xb0\xb8\ -\xb9\xad\x95\xd5f\xdd4\xc1\xfd\xe5\xfd\x8f/\xf0\xb1I\ -\xdc\x5c\x5c\xdc\xcd\x1b)\xf0\xac\x9aSKi\x88m\xec\ -\xb2\x07kx\x975\xae\xb3\xf6e=\xaf\xea\xf2\xbb\xdf\ -\xb6\x17\xb3\xab\xad\xe1X\x0c\xea^\xbc\xbe\x9d7\x83\xd0\ -_\xad\xf2b\x02\x7f\x82l\x95-Q\xe8\xe2\xd6\xab\x87\ -\x8c\x7f\x5c_\xde7\xa3\xcc\xb7\xcd\x90\xe3o\xed\xd8\xe7\ -?\xaf\x9b\xd1\xc7\x0a\xc5\xdf\xab\xf1~;\xbb\xbf\xbd\xfc\ -\xe1\xd7X@\x88T&\xd0\xa6\x82d\x802!,\x9a\ -\xed\xc54\xa8\x18z\xe2\xf3\x05\x9f\xb3\xc6\x00%\x0b \ -\xa6\xe32\xa71:*\xe6\xc2\xe0\xb1\xc8\xb9X\xa3\xb9\ -X\xa3\xb9m\x87\x8bNb\x98\xdcc?\xeb&B\xc4\ -4d\x22/%\x85f;\x9a\x08\x11\xcb\xb0\x89\xcc\xe5\ -\xfc\xec,\xdfa=`!Dl\xef\xb7\x90\xfa{\x84\ -\x85TX\xfa\x10\x0b\x1em!P<\x125\x17\x16\xf2\ -\xf4\x83\xb0\x80\x85\xca\x84\x8b\xa4\x13\xc5|J\xcf\x97E\ -\xc5E\xfd\xa32,\x85\xff\xd8d\xb8\x16F`\x8c%\ -\x09}\xf6,\xa8H\x88\x09-i\xa8C\xb3\xb5E0\ -\xb1>\xdb\x88]\xad\xd3Z)L\xeck\x16\xd1[\xed\ -\x17\x8b\xbf5\x8b:9\xeda\x9dk&\xb1\x13k\xf8\ -\xcd\xf1\xec\xa5\x82\xd6o/x\x00\x8f\x82H\xc29\xc1\ -\x02\xac\xc26\x99\xb6Wh\xc6\x93\xf6\x9e)\xf8\xc4\xb4\ -\xa8\x83\xf2d*YP\x08\xa5c\xe5\x89\x163\x03\xcf\xf9T&T@\x82\xa1\xc9\ -\xa7\xb63&!bi\x89\xbch \xab\xd7\x86\xc3R\ -\xcc\xdd8\x9f\x1a\x8e\x171\x22\xb7e\xce\xc5\x1a\xcd\xc5\ -\x1a\xcd\xed\xa2mi*f\x0b\xeeA\x83\x17\xd0\xe8\x0d\ -^\x1c\x0f\x80e\xa8)P\x1b\xe8f\xea\xd2\xefx\x9a\ -Q\x9b\x15\x9a\xc62A\xd4\xa2H<\x99\x12\x96d\xf1\ -\xac\xc1\x0bba2\x0ey\x0a^\xd4\x0b\x07g\xe6S\ -\xec\x82R$Y\x19\x974Tiv\x88]@\xe3\xfd\ -~\x06\x0c\xb6\xf739s[\x1fRo1\x18\xe2\xd9\ -\xf9\xf0P\xbc@R\x12\xb0=[+\xa3\xf5\xf92p\ -Xc\xa3 \x8c\xbe\xd1\xa7\x9d\x9d\xd3\xd9\xb6\xee\x12,\ -\xd6D$\xaa#\x87>\x1bPy{\x19\xb4\xbdK~\ -\x99\xd1\xc3\xfa\xa8~\x094z\xfd\x92\x8d\x0f\xf3\xb4\x90\ -9\x0b\xb6^G\x0b(\x1a\xca\x83\x07\x02uOY\x0e\ -\x07\x88\x9b\x8b\xe0\xe2\xae8\x99\x9a\x15\xe5\xd0\xe8x$\ -+\xe6j\x12O\x1e)\xb5\xb8%y\xf5Hh\x85\xd0\ -Y\xa5v\xe5RP\x90\x02;\x1e\xc9\xc5\x04v\x98o\ -\xd0|\x7f\x13#\x83\x1d\x9aX\x0d\xf3\x87\x9b\xd8\xf0s\ -\xebm\xe3\xd9Z\xe1\xfa\xda\x16\x19\xac\x1b\xbe\xeb\xb8\xa2\ -w\xb9\xef`\xf2\x00\x00\x1f\xde\xe4Is\x83\xc9\x1f\x22\ -lU\x09\xe5\x09\x16\xb1D\x94E\x0f\x01\x8a\x16\x13\xa2\ -b\xa1\x19\x13$\xa7\x92\xd2\x0e\x8b)\xb1\x08P\xb5u\ -\xd4\xe2\xe4\xe0\xfed\xeb\x1a\x05\x22-\xbc\xda\xfa\x0a\xcd\ -\xc5\x1a\xcd\xedb\xe0\x83\xc2i\xba\xc3\xdc\x9a\xfa\x91\xe6\ -\xd64\x8f5\xb7fxt{!\xf5~{9\xc4\xec\ -+1\x1a\xda\x04\x0b%\x0aac\x14X\x1c\x16YX\ -\xd0\xa3u\x8e\x82X\xc2\xa5\xb9\xc7\xa4E\xb5c0B\ -\xc5\xd0\x95\xeb\xc0\x18\x09\x8a\x09>\x8ej\xd6h.\xba\ -4u\x9c#`\x11\xbb\xcc\xbc%\x0e\xd8\xcb\xcb\xabf\ -\x8d\xceZ\x95\x0e\x0c\x8a\x17C\xf3\x9d\xf9\xc2\xe0\x888\ -u\xdb\x01N\x1b\x8e\xc1S\xa8F\xd1\x8d\xe3\xf4)\xcc\ -\xa3\xd8\xb2\xee\xd7\xa1\xc30k\xfa\x95_\xfe\xe2\x17\xbf\ -x\xd0\xb3\x16\x14\xc3\xda\xf0\xa7w\x80sv\xf6\xdey\ -\x86\x0ah\xa3e?g\xea\xe1\xfc\xfd\xeb\xcb\xfb\xf9\xfa\ -\xc0\xa9\x0b\xe8\x03\xdf\x8f\x03@M\xf3>\x005-\x07\ -\xd4\xdc\x0b>M\xc7#\xc3W\xdb\xda\x7f\xfd\xe5\xab?\ -\x7f\xf15}\xbd\x1dD\x15\xcf\x8as\x17\xffZ)U\ -\xb7n\xe9z\x0d\xee\xcb/\xff\xf8{\x80g+8\xd0\ -\x1aDU\xc9^6\x9f\x7f\xfe.\x1b\x19@d?\xe7\ -\xbd\xc4-\xbf\xee\x1b!r\xe2\x0e\x90\xa6\x14\x16\xae\xb8\ -\x22Pa\x11\xaf\xaf0\xac(\xab=\x01\x9cR\x94\x1f\ -F6\xe3\xd5@\xd8\xa0\x07\xef\xa2\x87vT\xe0\x95\xd2\ -\x87\xe9J\xe9\xc3\xf4P\xa5\xc7\x0d\xa5\xd7\x03\x94\xde\x0b\ -%\xd7\xc2k\xfb\xf3Pe\xa7\x0de\xf7]\xcan\x9d\ -\xb2K-{\x14\x00\xa0Zx[\xfe>9\xad\xb1\xcb\ -\xfe\xa5\xf7\xaf\xfb\x22\x16\xd9&b\xa9\xb1(P\x0d*\ -\x90\x0a\x83<\x05\x98\xc8YTC\x0fT\xe4\xd8Pd\ -?p\x91s\xe9y~\xdb\xc4a\xcdJ\xe4\xeb\xff|\ -\xf9\xdfM\x01\x174\xf3\xffy{y;?\xaf\x0b\xd7\ -w_\xa7\xfe\xe8L\x1b\xf9\xb0\xb2\xf8yu\xe9\xf3\x93\ -\xb6+\x85X\xf6i\xbf\xbd|\xf1\xe6\xd5\xc5\xff\x9e_\ -\xaca\xb5(\xc2\xd7\x97U\xf2\xd7\x0d\xe1\xf2\x99\xd3\xc7\ -\x87Z\xcdNW\xb869\xbf<\xa6SEb\x87O\ -\xdb\xabVE\xe8St\xab\xb5\xf8\xf2\xe9\xf9\xd5Zx\ -;\xaec\x1d_\xfc\x8d\xa5\x8f\x03\xc7j\x07\x9e7'\ -\x86\xec+\xbd`\xec\xd2\x80\xa7.%\x08\x1c\xbcS\x07\ -P\x82\xd8\xeb\xea\x8e)FA\xc2\xac\x0d\xa1\x12}X\ -\xd5\xf2\xd3T\xad\x8et\xfaGA\xce\xbb\x18\x9d\x17\xc7\ -t\xa9-\xc7K\x04\xd6\xe6\xb3F\xd2\xe40\x14\x11\xc4\ -\xd0\xbey\x8c\xce\xba\xc7\x22\x0e\x826\x9f\xb6\xcb\x1f?\ -`\x1dk\xba\x7f\xd0:\xae1\xcf\xe1\xebx\x95/j\ -\x86=E7d%U\xba1\x0ec\x01Q\xd0e)\ -/\xd6h.\xd6h\x9a@(JZ\xa0\xf2\xf0\xb2/\ -\x95\xf4\x98L\xa9\xa4&\xc7b\x1e\x1f&P\x12\xd4\x9d\ -&^L\x81\x5c'\x08%<\xc0\x9f\x0f\xd7\xc2\xd1=\ -q\x98\xfa\x11\xcd\xca\x1f\x9e9\xa2\xc8\x8f\xbbQ\x1f\ -w\xaa\x9b~\x22S\xdd\xf4IOu\xd3'<\xd5M\ -G\x9c\xea>~\xebw:Z\xeb\x9fb\x14\xe3\xa0\xb4\ -\xc9\xd4J\xb0\xb5\xe9#\xeb\xd4\x89\x5c\x0f\xe1\xd7\xa7\x98\ -%\xdb\xbf\x98Lc\x0f\x0f\xdf\xec\xbc\xbc\x9f\xdfn\xb5\ -\x1fpI\xca$\xf8\xacyn\xfe\xa7\xd9\xdb\xbb\xbb\xcb\ -\xd9\xf5\x1f\xae\xde\xb6\xcf\x0fqh\x070\xe7_\xcc\xbf\ -\xbb\x9c\xdd?\x1e\xb7\xc3)\x8a\xd2Y\x0a\xb0\xca\x94I\ -x\xb9(`)\xf9\xa8\xf5! v\x88\xfa\xa0\x22\xd0\ -\xfe\xe1|j\xbbv\xb9\x07\x1e\x18\x18\x22\xf7o\xe6\xcc\ -:f\xe1\x22\xe1Fu3\x98x\x81\x8c\xf0\xda\xbd`\ -1\xf5\xb4\xac\x1d\x0ceAb%\xdbkRa\x10G\ -\xe3\x92\x92\x16-\x88O2\xa7X\x04\xc4M\xa2\xfa\xaf\ -B\x1c\x86\x91O\x85\x9f\x22\x970\xc9\xa4G\x0d+\xd1\ -\xdeK\x1a\x982\x86\xb7\x5cDl\xde\xec\xb6\x8eC\x9d\ -\xbc\xf2\x92*Y;\x15\xe7\x22F\x16\x9d.\xc7K\xfb\ -\x1b\xa3\xea\x05\x85\xc5\x1c\x87\xd7L33?\xf9\xc9h\ -\xff\x12'\x98\xc5\xc5\xc3x\xc2X C\xd9\x9f\xef\x8d\ -\x10\x11\xf8 BL\xbc?BYB\x12\xac\x8e\xa9\xa1\ -3J-h\x0a\xd01j\x18\x02e\xc2\x85\x1f`\xa1\ -\xa2\x9a\xbc\xd8\xa9\x02\xc58\x83|2E\xcbB\x8c\x8e\ -\x13\xcbb\x0a\x969\x0a\x1d\x19F\x07e_tH\x8b\ -\x87\xa1U\xfb\xe1,d\x81\xd5~*Mg-<\x17\ -T'\x1e\xf6k\x88%\x17\x7f\x13\xd8\x1f\x05V\xe5\xe1\ -\x99\xe4\xcc\x8f\xc5F\xaa\x85@\xb5\x8cj-\x0b\xc3\xe0\ -@\xb4\x07k\xc1D\x1e\x85\x0e\x8e\xb0\x91\x8f\xcb\xc7$\ -\x1a\x89\xc6D\xa0X\xfb\xa7\x13\xa6\x02\x16\x064\x06!\ -\xc9c\xfa\x18\xa6\x0a\x8d\x14f`\xa2\xda\xafh\x09\x06\ -B\xb2\xda~\x80\x0a#\x92\x0c\xaf:v\x14\x0a[\x80\ -C\x8b\x0d\x94\xa2\xca\xea\x93)\xc9(<|\x0b\x8b\x81\ -\xbd\xbd\x8a/\x1b}\x85\x05I\x0a$j\x85%sA\ -\x13Z\xb7\x07Sq\x0ac\xd9\xab[\x9aLu\xd9\xea\ -\x96\xadj\x99k\xa30\xd2A\x9f#\x8c\xfbb\xa4\x92\ -\x05\x0cY:\x9e7\x8b\xbbXg\xc0\x85&\xc5,\xab\ -\xe5T\x9a\xcd\x18eq4\xd5\xc8\xf9\x94\x1e`B\x06\ -\xc4\xe4vh\xc3`h\xe9\x8d=aQ`\xe5q8\ -\x0d\xfaf\x17\xa2]q:t\xcfr\xa0\x1e\xaf\x17\x05\ -\x941=\xd4O\xdf\x07\x93\xb2\x8c\xf0\xc1?\xf1>\x9c\ -\x94\xc7\xf7\xe1\xbb\xece\xe6\xbe\xbd\xcc\xba\xfd\x06\xa83\ -?;?\xf7m7\xce\x89\xc6\xb8\xbd\xcc8\xfe\x04\x8d\ ->8P\x8es\x16\x00\xa3\x1dm\x7f3\xc6\xf1\xe0\x80\ -c\xc1A\xc7\x83C\xf6\xdf\xe9\xeeB\xf4\xde\xf6\xe0B\ -\xb2}{x)M\xb2\xdd7\x04\xe6Y\x9e\xcdp\xe7\ -\x0d\x81\xca\x12\xc3\xcc\xcf\xce\x9bF\x8a;\x9e4\xe1B\ -\xb6?\x9c$\x8c\xef\x85\x93\x84\xf90G%\x8c\xb7\xcd\ -\x22\xa4\xe9\xe0\xf9l\xad\x8c\xba\x85\x0cl\xd3p\x95E\ -\x92\x07\xae\x8b\xf0\xbe\xbd\x96\xc2\xb9\xc6%\x800=6\ -\xb8\xcb\xf9\xacM\xdb\xee\xe7\x14\xc1\xed=\xf1\xf9E\x93\ -\xd6\xd0\x1ca\x1c\x9c9\xe4] -\xe6\xbbz\x17\x12\ -\xc0m\x0e^9kv[\xed\xd8\x1cH\x80\xc7;\xdb\ -\xe1\x18\xb9\xcf!\x8fx\x97;rD\xf0\x03n1.\ -\xa1.\xcd\xda\xf8\xe6\xd0+\x06\x85\xb1\x1f\xa7\x1c\xc6\xe9\ -\x90\xa3\xcb\xf5\xb1\xeb\xfa\xf8\xb6o\x0c\xdc\x1d'\x1f\x18\ -#f\x84^\x8c\x92\x861\x1a;/\xb1>\xcf\xb1>\ -\x19\xb2>cR\xa7Sv\xd8\xae\x1a\xb1\xcfAq\xb9\ -\xc3>\x7f\xc3\xb88\xa7\xad\x0f\x8a\xcb\x1d6\xcb\xcf\xe4\ -\x0c\xe6\xf2\x0e\xeb\xf7y\x9a\x9e\x93F\xd7\xcf\xedd!\ -\xdd\xe1L\xf0\xa1\xf3\xc5\x87\xcf\x22\x1d>\xe1|\xf88\ -\xd4\x83/*\xce\xec3\x7f\x07\x19m\xfe\xd3\x87\xa5?\ -\x13$\x99\xac\x98;\x14\xa0\xd0\xac\x13\x09P\x10\x03\xba\ -\x93\xca\xc5CP\xb9\ -\xbep#.L\xfe\xd0z\xee\xde\xdc\xceg\xe7\x7f\x99\ -\xdf\xbf\xbeiK?\xbfh%o\x85\x15\x940\x041\ -n\x91\x8a\x82\x86\x1c\xcde\x116\x0c\xf3Q\xed\x87\x8e\ -\xdaSZq\x05\xeft\x0c\x1e%9\x99+l(\x85\ -\x5c#\xaa\xfbp)\xae\xfd\x90\x8d\xd0\x96\xf2\x88\xde\xc2\ -\xa0\x1e\xd8h\x85E\x14\xea^V\xa7\x02\x04\x81\xb5\xcd\ -\xac\xd2\x9cui\x86\xc6\xe2P4\xd0(t\x82\xc5\xcd\ -\xdd\xbc\x9d\xe0\xa4b\x19F\x92\xed\x0f(\x11\x1e\x19M\ -6k\x01\x0f\xb4\x89\x96\x14LH\x19\xd3\xca\xc8\x07!\ -\x14\xc9\x9f[Y7l\x0f\xb2\x91\xc1\xc7\xf8V\xa6\x85\ -\xd9T:\xad\x8c\x8b\x0b\x19\xc7qZ\x1ao\xe3W\xc6\ -\xb7\xb4\xb0\x12di\xb5\xa5M\xd1\x8aYH\xd43V\ -W\x89\xceV\x88\x86\xdb\x1ai\x98\x81\xb7\x91\xb8\x12\x92\ -\xb8\xb4\x97A.,2\x81\x22\x08\x06\x88\x13\x06*\xca\ -):Q(\xa0\xaa\xa9\xa3\x1a\x1a\x1e\xb5\xa1\x15TM\ -\x8dN`\x07RHW\xa2\xba\x04\x0b\xee\x8c\xe7\xa4\xe8\ -\x10\x5c\xef6%v=PO\x85\x83\xbe\xdb\x04\x0e\xb4\ -X\x842\x07\x97\x03\x9c{\x9bv}a\x97I\x07z\ -\x9b\x8b\xeb\xacG,\x07`b\x1f\xd4X\xe7\xb6\xf3\x02\ -\x08\x0a<\xday\xf7$\xb8\xbf\xc6f.C\x1a\x03\xa0\ -I\xec\xa8\xb1\x99\xdb\xb0\xc6\x88\xb3\xdcYc3\x8f1\ -\x1a\x9bU\x8dW\xd8Z\xecp\xcc\xfa\xc5\x19^\xd0\xee\ -k\x144|\xfer\xf73\xa0\x19\x83+\xf3w\xe1\x80\ -\x1dNi>\xa3\x99\xaeU\xe6\x188e\x9boE\x9c\ -\x9f\x9f\xedl@\xb2U\x939\xbfx\xb9\xbb\x01\x89\x8f\ -\xd1\x98qHc\x07v\x80\x9d5f\x1e\xd6\xf8\xe5\x85\ -\xce\x01v\xd6\x98u\x84\xc6\x22v\x94ua&\x12G\ -r\x8b&\x0ac4\x86a\xb7\x188\xe7\xf9\xce\x1a\x83\ -\x0dk\xcc\xe1\xe7ko\x87\x875\x868\xf6\xe9\xd9f\ -f}\x072\xd8A\xd6\x17p\x9a\x92\xf2\xc4J\x06\xa3\ -(\xb6\x1f5\x99z\xf1@Ik\x7f`Q5\x0cn\ -C*\xa2\x12d\x88\x93\xa9e\xb1\x0cC{\xfe\x9eH\ -\xfe\xcd\xec\xbc{\xbe\xb6\x03\xb0v\xde\x85\x01\x17\x0d\xa8\ -S;\xab$\x17\xef\x92\xdc.(TSb\xc4{\xa6\ -\xfe\xe0\xad\xcf$\x11\xea\xca\x9c:\xa5\x1cQG\x89R\ -7>n3\x9dz\xf0\xc2\x8b\xf5\x16^\xa9\x0eU\xb1\ -@J\xe6Ci\x979\x08\x98)5\x0e\xd7\x82\x91\xc8\ -\xdc]2\x03\xe0\xa1\xdb\xe8\xd5?\x96\x1b\xde\x1e\xedE\ -\xc3!-Z\xab\xe3\x00\x13\xa5\xe7\x1f\x12);\xc8\xdb\ -o(\x82\x94\xc04\x81b\x98\xac\x12\xcf\xfbq\xd9\xb9\ -n\xc8\x22b\xa5n\x1c \xf3C\xe2\xe4\xf0\xb1\xe3\xc4\ -%\x0dA*LQ\x885\xc5?(L\xf43L\x9d\ -\xc0\xad7\xa6\xab\xaf\xe4T\x0b\xb7\xba\xd6\xed\xed\xf5\xd5\ -\xcf\xda\xdd\xe5:\xb6DF\xf1\x0f\xefqM\xb8W#\ -\xa9{^\x8c\x0ap8Rw&,\x91\x1d\x1f\xd4Z\ -#i\x15\x85\xe2\x14\x19z\x10\xfb\x99\xf0\xc1\x9d\xa8\xb9\ -\xf4*\x1f\xd0\x99\xb1)\x90\xe6\xaeu\xf6\x14\x8a\x06\x98\ -x\xb5\xc1,\xc9\xc2\x5c\xd7\x91D\x14c\x96\xf8\xa8\xba\ -Q\x0a\xdc\xe5p6\xeb,\xbf$+\xc9f\xab\xcb/\ -E\xdc\x8d\xaa\xceT\x802T>\x9cFL\x8c\x87\xf1\ -Od\xc4F9\xc1\xba,O\xb3\x10i\x80w\x0fH\ -Mg\xc2Z\xf3\x95\xe6\x11\x05\xb1B\x14\xa1\xf9AQ\ -\xe0\x03\xa1\xc0L\xd4\xa2P\x12\x15R:h jq\ -3\x8f\x8a\x06\x16\x08I\xa8\x11$c\xe1\x0e\x0d-=\ -8#\xe9\x81\xc1`b\xef\x05C\xb8\x9e/hPR\ -\x94\x14\xab\xeb\x02-\xc1\xa0\xb5\x07i\x88\xaa\x0d\x0f\xe3\ -\xf8\xe1\xfc\x10y\xc0.\xed\x15\x8a\x00\x81UOeR\ -\xc0\x0d\xa5\xbe\xd4\xa845\xd4\x87\x92F\x84r\xe0\xe3\ -\xf7(\x13zO\xf1\x87<\xc0j\xda\x82 \x10\xa1\x13\ -(A\x01f\x8b\xf7\x0bf\xe2\x92\xd6^F\x183\xf9\ -\x04\x13\x8b\x88\x11O\x8c\x0a)'\xd8P\xf8\xd1\x8e\xf5\ -\x8c\x0b\x08\xbak}G/%=\x05\xa2\xbe\x93\xe4\xa2\ -\x8a\x84\xf5]\xbfb\xd1\xa4\xcc\xfa\xa19.\xe2\xe2F\ -\x0b\x80\xcf\xae.\xdf\xfcuv\xff\xba\xbd\xf9x\xbdy\ -\x12\xff\x91\x8a\x91\xda\x1d0o\x9a\xcb\xee\xd0\xbf]\xcf\ -{\xf5\xe2\xed\xed\xd5\xaf\x7f\xb5\x8a2#\xf3\xf3\xdf\xb4\ -w;\xb3\x00w\xf7\xb77\xdf\xcc_\x5c\xdf\x5c\xcf\x1f\ -\xae\xa7\x8bO\x12\xbc@+\x92\x0a\x00!\x8f7\xda\xfa\ -lj\xfe\xc5\xed\xcd\xdb\xeb\xf3n\xe6\x7f\xdf\x5c^\xaf\ -\xe6~{\xd9\xac\xeb\xbd\xbal\xfe\xf7\xe2\xe9\xf1\xf3\xd9\ -\xdd\xeb\xd9\xed\xed\xec\xc7\x07i5w9\x07\xf1B\x0a\ -\xeaC\xf6\xda\x17\xa1\x1a\xbd\xffr2\xc5\x90\xe2l\xa2\ -\x13\xf2\x92\xc2\xae'\x9f7\xb9)%]\xdd\x9er\xf1\ -dJ\xa0\x85@\xcd'\x8c%0\xd0\xad\xc9D*\xe9\ -@6\xe1,\x1a\xe4F-\x03\x22(\xe9.1\x11/\ -\x1a\x99\xc9m\xa6\x94\xd0\xd4\x89F!\x22\xc3\x87<\x83\ -$\x9fX\x14\xf7\x14\xc8\x93\xaf\xba\xd9i%\x88@\xb0\ -e[\xb3\x11\xa0\x84\xb3G\x9b\x89\x85U\xbc\xcd\x94\x82\ -\x14\xd4f\xa2\x17\x86&\xd5\xcc\x96/*\x15\xb4\x8c.\ -m\xab\xafDA\x0c\xe4.\x0b\x14)al\x5c\x85u\ -3k\xc1\xbe\xeafW5>_f\x1b\xb2\x85B\ -(Q_<\xc6\x93\xc2\x8f\xca\xf4|\xee?\xa8\x7f\xfd\ -\x86\xd3\x17\xc8_\xac\xbde\xee\xfbf}H?+\xd6\ -\xdf\x7f\x8e\x9f\xbf\xcb\xaa\xb0*\xf7\xf3\xb3~~\xf0e\ -*\xc4\x1a?\xc3\x94~~\xb1\x81\x1f\xc4\xe7\x00k\xfc\ -\x5c\xbd\x9f_\xc2&~\xfe\xfb5~\x0f.\xb7\x1a\xcd\ -.\xab\xc5\x80zv\x99\x83\xecp\x98\xd2\xf9|>t\ -\xdaI\x97\xb5\xedp\x84\x07\xcdc~\xbe\xb6rb\xc4\ -\xfa8\x83}6\xdd\x1b\x1d\xf6\xf0\x97.k\x19q\xb6\ -\xd4h8R{4N\xdf^c\xe3y\xee\xa0q\xe6\ -\xbfQc\x91\xdc\xc3\x00Dq{8`\xf1\xb7\xfd\xe1\ -b\xdc\x03\xc7\x00\xeb\x0a\xc7\xa8c\x17\xc8Y6\x1f\xbb\ -`\xd2\x9e\xba@\xc5\xda?\xef;{\x01)\x9a[&\ -\xfb\x1e\xbd\x00\xf5\xe9\xbd\xce]\xa8\xdb\x18\xa1 Ag\ -[\xa7qAM`]y\x87\xeb\x085\xf2\xa94\x83\ -\xeb{@\xb9\xfd{\xecP\x093E'\x88\xc5m\x22\ -%\x93\x84l\xcc\xb1\xc9\x1c\xc3{y|\xff\x03N%\ -\x0a!\xa1b}e\xa2E-\xba\xf0X\x96$\x86\x88\ -\x95]\x06\x9cd\x1f\x03<0\x1a\x9e\x9f\xb0\xf1x\x0c\ -\xa2\xc3\x86{\x1b\x8f\x14\x0a\xef\xc2\x93\xd8\x96\xda\xc1\xba\ -K\x0d\xea7\xf5\xdb\xfb\x1eJ\xb2\xc5fi\x82\x09\x8c\ -R\xdd\x06U\x17\x80\x9f\xa6\xea2B\xf5\xc11W\xdd\ -\xa6\x93X\x02\x14:\xc3U\xf5\x02\x1a\x8aR\x07`Y\ -@5 \x9f\xc6\x0d\xca\xc5\x18hx4V\x143%\ -'^X1\x89s>\x8d\xc94\x0a\x18\x93\xb0\xb6\xbf\ -\xb0\x90;!\xcadJP\x88\xc4\xc9\x9aK.\x99f\ -\x9c\xfb\x0f\xda\x88\x9d\x86\x9b\x8e\xc4\x9e rQ\x0f$\ -\xad{\x0b\xa5(#i\x1d\xc4b\x97\xe4\xec]\x92\xb3\ -.\xc9\x00\x90V\x14<\xc3\xf2ax\xab\x08\x89>\x99\ -\x1a\x97\x16<\xc3\xc9\x94\xa9\xa4\x03\xa2\x8eB\xec\xa8\xae\ -8\xad\xb01hg\xe5R\x14\x03\xc3\xce6B\xa6\x02\ -\x8aa\xb5\xd5\x09\x17\xcf\x04\xb2=}\xb1\x17\x1533\ -?\x8c?\xb6m6\xb6\xcb\xdeF\x85j\x1d\x9bB+\ -\x10VM\xca\xb1\xb8+Q\xdd\xad\xda\xa58\xebR\x0c\ -O\x22\x98B\xa04H\x91a*\xd0d\xda\x82\x06\x98\ -!\x13.\x9c@\x086A/\x0c\x09\xd6\xdcV/\x96\ -L\xe3\x1a\xa5\x0d;u\x8b\xdc\xdb\xa9\xb7pdp5\ -\xb1\xb4\xc2\xceH\x5c\x9d:A\x11\xf2\x1a\x0cU\x9aA\ -\x13CwR\x89\x87f\x08\xee\x84\xe1\x13+\xaaJ)\ -6\x99JIAs\xc0Q66\xec\xfd\x15\xf1\xa7c\ -c\xcc\x07\xb61\x1a\xb61\xdb\xdb\xc6(J\x0a%\xd5\ -5Jb\x85HS\xde\x99\x89\xe5\xf0\xea\xc6\x1c\x8b\x91\ -\x09\xc8\x9e6\x86%\xc4\xc8\x95\x0fec0lc\xc0\ -?w\x8e\x151\xe1m:\xc7\x9f=\x7f/~8\xd6\ -\xf3\xff\xf4[%\xc76\xad\xf2g\xcf\xdfgc\xac\xc7\ -\xf0\xfc\x87\x8c\x0a\x0e\x14\xa9\x8c\xb11\x1a\x19]\xfc\xf4\ -\x83|\xda*\xc8\xffy\xf8\xbd\xb1-\x92\x8c\x18~\x0f\ -\xb4\xc5\xe1\xa9\x97\xf1\xb3\xbb\xc0\x18\xf1\xd2\xe7\x13\xc4O\x0f\x8d\x9f\ -}\xc4\xcb%\x8e\x1fV\xa1\x1c\xe6\x9dk\xfd\x5c^\xf7\ -\x8bx\x0fN\xeb\xf5\xfc\xf2\xd5\xeb\x86\x1e\x8bf\x90\xe9\ -\x12\xce\x87=Y\x99\xf8\xb4\x85n\xb1\xfb\xb3\xa5K\xe0\ -d\xc0e\xd9\x16t\xa2\x98\xa6\xe0\x1b\x8b\xb2\xf6\xfd\xbd\ -\xfe/\xe8\x91\xbc\xf7\xeb{XX\xdc\xd4\x16\x05\xdf\xa8\ -t\xfd\xfc^\x8f\xf6\x16\xf8\x8e\xf6\xa8\x9eB]\xed\xc1\ -#\x1d\xc5pU}\x12\xf1\xa0\xae\xfaH\xc4\x89\x07S\ -\xdf\x82{>>\xc8\x84\x892^{e>PY\x95\ -\xb5\xa7\xaa\xc0\x00Xu|a9\xed@\x85\xe5\x8c\x1e\ -`%w+\xe8\xaa\xa3\x1a\xf2\x9e\x18l\xb8\x9c,\x9a\ -b\x11\xa34\x9fO\x97>\xd4h1\x0f\xe2\x854\xb6\ -\x0d\x82\xeb\xd1\x07D\xa9_\xe3\xd7O}\xc0\xd4\xc0\xa3\ -\xb0\xd6a5[\x16%\xa1\xc7\x8d\x97\xa4\x5c\x00\xa3n\ -\x0c\xae\xcf<\xf5\x0bdZ8{>O\x1e#w'\ -\xf5lNB\xd8\x7f\xa7V\xe5;{\xf1\xed\xe5\xf9_\ -\x9bM\xe7\xf7\x7fkn\xeeX\xd0a\x0eEG\xf0\x18\ -T\xeb\xcb\xc5\xdfV{,\x87{\x9e\x90\xc9\xd4\x5c\xea\ -\xcb!s\xa9\xd3\x07\x1c\xc5\x9d\xb9s\xbcY\xd6I\xf9\ -zw\xef7B\x89\xf2^\xe3\xa1D\xeb\x07\x80\xacM\ -C\xa7D\xf7\x18\x10%F?k\xc66\x0d\xb1.\x88\ -$\xbd\xfci\x83\x05\xc9y\x9b\x06\xf9\xb3\x8b\x02\x00\xf6\ -\xcb\xa0~\x19\x0am\x1a\x94!\xc4\x90\x99\xd9/C6\ -\xc8\xf06\x0d\xcb`\x8b\xcd26T\xb3I\x9b\x86e\ -H\x0e\xe8\xb1\xa1\xbe]\xdb4,\xc3@{\xf9\xf3\x86\ -\xfa\x8eY\x9b\x86\xf9;\xe2F\x1dxC}\xcf\xb8M\ -\xc32\x02e\xa3Mq\x7f}\xdf]^}7\xbf\x1d\ -\x16\x91\x88\x9bEl\xa8\xee\xf3\x8b6m!Cx3\ -T\xb1\x01\xaa\x8b&\x0d\xcbPt\x09\x8f\xe8\x95!\x1b\ -\xaa<\xa5M\xc32\x98t\xa3\x1eB\x1b\xcc\xf6\xacM\ -\xc32T137\xe8!\x1b\x9a\x9f\xb6iXF\x88\ -\x0a\x98\xf7\xcb\xd8P\xe7Jm\x1a\x94a\x10\x06\x00\xd4\ -/cC\x9d\x0b\xb7iX\x86\xa0m\xb4]\xc5~\x19\ -\x9cm\x1a\x96\x91\x14\x1aD\xfd2x\x83\x0c\x1eV\xc2\ -9}c\x85\xab\x8e\xee\xfb\x22#2\xd3\xfbe\xf8\x86\ -\x06h\xb3\x98\x0d5\x8e\x9eo\xef\x8c;\x84d8\xfa\ -\xb9]~\x92R\xc5\xac\xb3v\x91J\xa6\xad\xac]\x8c\ -\xe2NJ}\xcb\x06\xc3\xed\xfd1Mxl\xf0~\xd9\ -\xa6}c\x9a\x88M\x87ax\x9b\x06\xeb\x15$\xa5\x9f\ -?\xf5\xf3\x7f\x99m\x1a\xe4\x8f \xd9\xcf_6\xd8\xcc\ -\xbcM\xc3\xfc\x85\x09\x93\xb8_\x86m\x90\x91M\x1a\xd6\ -\x81P\xad\x9f\x7fl\xb2\xfb6\x0d\xf3\xb7\xb4M\x9dB\ -\xe4\x86z6o\xd3pl\x99\xb1\x81?\x1d\x80\x7fn\ -\x8c]#7\xc5\x95\xd2\xa4mb\xbe\xe0\xcd8m\xa8\ -k\xc16\x0d\xcbH\xd5\xcd26\x8d#\xb4M[\x04\ -\x01\x19\x91\xda\xefK\x01F\xfbkuHS\xe2~\x19\ -\xb4A\xc6\xbcM\xc32R\xa2\x9f\xff\xa6\xfa\xbeh\xd3\ -\x16\x9d\xb3n\x0e\x00`C};\xb4iXF\xf2\xe6\ -\x18\x1cb\x83\x8c\x97m\xda\xa2\x7f\xd6\xd8\xd8?#l\ -\x90q\xd1\xa6a\x19\x03>$\x91F\xda\x14\x1ek\xee\ -\xc1#\x0b\x12\x81o\x98\x810\xf7\x12\xee\x80\x87\x9c\x87\ -\x08\xa3\x9e>\xdbd\xf4`\x1e`C\xa7a6\xda\xe1\ -\x22K\xf4\xf3\x8f\x0d\x1d7\xb5i\x8b\x81#\xf7;s\ -\x87\x0d\x9d\xdelx\xe4k\x94\x1b[D8m\x10\x80\ -m\x1a\x8eX\x95bc\x8f\xe4\x1b*9\xcf\xdb\xf4\x11\ -G\xacV\x80X\xea\xd2;\xc5\x82\xc0J\xefD\xac\xcc\ -\xd1\x1b\xb1\xaa\xf4X\xbf\xda\x86\xb0\xf2e\x9b\xf6\x8eX\ -u\x83aR\xb4i\x9b\xde\x937\xf5\x0aa0\xa2^\ -G\x9fd\x97\xb2\xcf\xb9ni\xdbO\x9c\xf3\xffk\xef\ -\xca\x9a\xdb8\x92\xf4\xf3\xfaW0\xfc\xa6X\xaa\x94\xf7\ -\xa1\xf1:B\xa6\xad\x89\x89\xf0\xecN\xec<\xec\xa3\x83\ -\x92@\x8df$\xd3AQ\xbe~\xfdVv\x03l4\ -\x89&\x05\x8a\x1c\xcb\x07a\x19\xa8\xaf\xd1\xd9\x95_\xe5\ -U\x05\xa0\xcb\x8f\x85\x9e\xbf\xff]\xce\xe2\xfd\xd7\xce\x9f\ -\x9d\x9c\xe8\xd55\xf9\xdb\xdf\xe4\x8c%n\xc1\x06+\xec\ -q\xd3\xbf\xdeg\xdc\xd1\xe5\x05\xc9\xb4\xcf=\xce\xe4\xc5\ -\x9d\x92ax\x1b2\x8c\xdf\x9f\x8c\xd5\x8b\x93\xda\xc4\xf3\ -=M\x83M\x17\xd8X\x12}\x977\xc0\xe3\xb0\xdb\xd0\ -\x11q_w@\xe4\x84_\xf2~\x80\x00\xb7\xa0C\x80\ -\xf6\xb0\x0e=9\xd9\xe3~\x80 \x0bt,\x8a\xbeK\ -:\xc8nC\x07\xeda\x1d\xe5\xdf{\xd0\xc1\xb0_\xe8\ -\xb8[:8nC\x87\xc0>t\x10\xeeA\x87\xec\x15\ -I\x09\xef\x96\x8e\xe4\xdb\xd0\x91z_\xb1C\xd2\xffM\ -\xb1cY\xe7\x05\xae\x14\xf8\xe6}\xc9\xf5\x04/\xcf\xf7\ -o\xd8\x7f\x97\x15\xdecWi\xe1\x14~~\xbdd\xbc\ -*\xd9?\xc06\x14o\x93f\x15y/W9\x86\x17\ -\xefk\x1b\x8a\x0bivY\xf4\x1d\xba\x8aY\xde\x82\x0e\ -s\xbc/W1\xe7\x8f\xd5U,\xf2\xc6\xdd\xb9\xe1\xe4\ -$`OW\xb1\xac\xba\xf1\x16\x92\xe1&W\xb1\xe4\x0f\ -\xb0\x0d\xc7\xdb\xd4\x1c\x8et_\xb6\xe1(\xbf`\x09\xe6\ -\xcc\xb7\xa1\x83\xf5\xbeJ0g\xdf\xaf\x04\xbb\xe3[2\ -3`\xfe*o\xc9|\xfb\xfd>\xa6\x1d\x12\xfc\x9b\xc5\ -m\x15\xfcNvr}\x88\xd9\xb2\xfe\xea\xe6&\x0f\xa6\ -\x1fM\xc2\xd6\x86\xeaH\x8dA\xfcb\xdd\x87\xb3\xa9\x86\ -\xde\x91\x8aq\x8d\x8a\xfe\xe9]l\xf8\x10N,\x9b;\ -\x14\x04\xf6\x07\xd5\xafz\x9aqP\xfd\x10\xd6Z\xb0\xd5\ -c?\xfd\xe7;\x8a\xde\x9e\x80\xfcf1\x15x\xee\xb1\ -\xdfdJc\x99\x96\xabK\x87\x0elm\x8b\x1b\xd6\x94\ -u\xda\x147\xa5\xb7\xcd\xeeH\x0d\x84e=\x02\xf7\xd1\ -C\xb7T\xe0Y\xef\xc3t\xd6\xfbj\xdfQ\xef\xf1\x9a\ -\xde\xf3\x1d\xf4\xde\x1b%O\x9d\xd7j\xdeU\xdf\xe9\x9a\ -\xbe\xeb>}\xb7\xad\xbe\xcb\xd4\xf7h\x00@S\xe7m\ -h\x8fQ\xfd\xc5\xea\xe4\xed\xe7\x9fM{\xa2\x1d\xbfY\ -\xbd\xf8\xfe\xd5\xea\x87\x99\x22?\xbc\xfa\xf6\xc5\xe9\x0f\x0f\ -7_e\xb7\xf0Ow\x1d\xdf|\x81\x1d\x88.E\xfc\ -\xe3\x97\xab\xbe\x01S\x7f\xcf\x7f}\xba\xe3\xd0:\xd3\x0c\ -\x9fx\x8c\xea\xbe{\xd5\xb7f;}\xbd\xea\xf1\xe0\xf9\ -\xaa$n\x0e\x9c\xf5K\xec\xc0O\x9f\xfd\xb3\x13\xbd\xeb\ -\xc8\xb3\xd3\xb3\x17\xab\xb3\x8b+\xe0\x0c\x1e\xb2^\x1f\x07\ -\xab\xbf\x91\xbc\xea\xcf\x06\x1e\x8b\x83\x8b\xb1yv\xdc\xb9\ -\x9eu\xfe\xe7\xd3\xd3\x1e\xaa\xa8\xa9\x12!\xeb\xfc\xe0\xf8\ -=\xe7\xa6$pe=\xb7V\xdb\xa9\xa9G\x04\xed\xa4\ -\xb2b\x98\xc1\xceC?\x95X\xbf$\xf0\xdd\xd9Y7\ -\x8f\x87\xaf\x8f\x7fZ\xf5\x9e\x7f]O\xdf\xac\x13\xdd?\ -N\x7f(\xda*k\xbc[MPQ\xfcv\x02'Y\ -\xc3\x91\x87\xcf\x9e\x9d\xfe\xb8>\xba\x95\xeaK\xd0T_\ -\xfc\xb9\xb7\x9e\x9e\x9d\xbe\xf9\xdb\xd9\x0a\xc4\xfe\xbe:?\ -\x7f\xf5\xed\xcb2\xf3\xfa\x1b\xf3\xea\x8f?\xd5)kh\ -L\xf8\x95\x8b\xbf\xfbq\x06\xfd\xb4\x0d\xbd\xedc\xd5\xe5\ -\x14{\xcd\xcc\xfc\xf2\x81\x9f\xae\x1c\xd8\x8c\x17\x00\xf4\xfa\ -w\x0d\xae\xde|\xb7\x13\xdf26\x9a\xde\xba\x85\xca\x84\ -\xae\xaf\xd8/\xb8\xc1\xbe=~\xf6z5qY\x7f\xdf\ -\xbfz\xfb\xaa\x83#6:\xd5Uw\xfa\xfc\x93\xcf\xde\ -\xac\xce\x8f_\x1c\x9f\x1fo\xcci\xd3\xee3\xebO\xfe\ -\xe3\xb3o\xdf>~{\xf2C\x7f5\xbe|\xfd\xea\xf9\ -\xeam\xb5\xa6\xe6\xe0\xbe\x9b\xc2\xb0H\x9c\xff\xbc\x84b\ -\xfa\x11\xc9\xd4\x1c\xce\xfc\xcb\x97\x1d`4\x17&\x96\xea\ -b]\xe6\xd1t\x9d\xed\xab\xfe\xfd\xf4\xdd\xd9\xf3\xd5\x17\ -\xb5\xa3\xda\xdb[\x5c\xb2\x5c\xab;\xe2\x9b\xafW'\xe7\ -\xff3\x0c\xeeD\xcc\xfa2\xa7\xdf\x9d\xf7\xdd\xd9~\x1e\ -~\x1a\xb16\x9aI\xd5\xf3\xe3\xb3\x97\xab\xf3\x0d\x5c\xf2\ -\xe6H\xe92v\xa3\xffRb\xf5\xb4\xd7\x0a\xc7\xbd+\ -\x7f\xfb\xef?\x93\x8c\x8dbs-k\x0b\xad\x13\xaaj\ -\xea#\xda\x1d\xa5\xfb\xdf4\x82\xe3/.jPO\x8e\ -_\xaf=\xbc\x9fq\xbe:Z\x9b\xcf\xd3\xe1o\xc0\xbf\ -=\xfd\xebtdz\x7fw\x9e\xdas\xee\xf8\xf9$e\ -\xe8E\xb1<\xefG\xc1\x05N\x8a\xdeB\x7f\xb44g\ -%\xc5\xdf\x22\x11\x05/[I\x1d\x1b=\xe5\xb3\xb3\x17\ -'\x8f\xff\xf7\xcb\xa7}s\xd0\xe7\x8f\xff\xef\xf4\xec_\ -\xa3;\x16z\xfc\xec\xf4]'\xa3G\xae\x17\xcf\x1f\x9f\ -\x8c\xd7{\xf5\xa6G\xf6Go\xbf\x7f\xf9\x9f?\xbey\ -\xfd\xd9\xa3\xe9@\xbd\xa7\x82U\x9d\xbe\x11p\xb6z[\ -n\xd0\x9d\xfa\x1f\xe7\xe7\xdf=~\xf4\xe8\xbb\xbe\xa9a\ -;={\xd9\xcf\xeb\xff\xbdyU'<\xfa\xfby\xdf\ -\xd0\xf0/%wt\xfbu?\xfa\xab\x8b\xae=\xdax\ -\xf9\xe7\x9f|\xb2\xab\xd8\xee4M3\x85=\x8a\xd7\x9b\ -\xab\x82\x22~\x9c\x0f\xceg\x95\x8b\xabw\xec\xb5\x91\xd9\ -4s\xc4\xaa,w\x0ai\x80\x8aK\x920\x8c#\xe7\ -\x92bY\x92j,IR\x8d\xa3'1\x93D\xb0,\ -)M\x96$E>yz\xc4sI\xb4(\x09Y}\ -I\xd2\x17|t\xf4\xe5\xa5>\xc9\xb2$\xe7EI_\ -\xcaW\xf4U\xce%-3N\x90\xb9$\xe9\xab\xa3\xa7\ -\xf4T\xe7\x92bY\x92\xb0.Iz\xdaiz:\xb7\ -\x02\x86eIN\xb4(i\xf8\x9bK\x1a\x19\xbf\xfc\x99\ -\xf8M\x8e\x11\xfb9F\xde\xb5g(\xab\xaa\xcf5\xf1\ -\x91\x93\xddc\x8el\x86\xa2\xcb\xb6\x18O\xe2h&O\ -p#oa\xec)\xcd\x22\x96\xc7\xbf\x1es\x89\xbe<\ -jl\xbc<\xfe\xf5\x98K\xca\xbb\x1a\x7f\xc5EI\xca\ -\xb8O\x9f\x94\x17%\x85\xc8\xb2\x9f|q\x85'\xd5\x05\ -I\xb8(\x04\xbb\xdb\xf2\x5c\x88/\x19\xf6\xc2\x92\xe1\xb4\ -np\xff\x8b\x03;i\xdac\xd4\x8c\xf6e\xa8\xdcE\ -y.D\xd6\x0c\xcd\xf8\xb8\x91!\x84o\xeem\xdd\xe1\ -\xc3\x89\xc9; \xc6\xf1v\xc4\xe07\xf7\xb4\xa4\xf1\xc1\ -\xb4\xb8\xedK\x0b'\x7f\xc11\x17\x12\xb7\xa3\x85\xbe\xb9\ -\xb7\xd5\x92\xfd\x88\x89\x88'1O\xc9\xb1\x1c\xb4\x00\x9c\ -\x16%\x1d\xc5W\x91sI\xba,\xc9|1\x90>\xf9\ -\xe2\xc9\xd1\x93K\x92\xfc\x9a\x22H|I\xd2\x91\x1e\xd9\ -\x91\xcc%]\x93&\x10u\xb1\x08\xfa\xa2?\xe6\xc1=\ -\x97\xd3\x04\x03\xd2rp\xaf\xc7\x5c\xd22\xe3\x82\xd7\xa4\ -.\xef\x0f\x9bKZf\x5c\xcdsQ\xd2\x97\xf5\x98K\ -Zb\x1c\xf7\xf0\xb1\xcc\x05\xf7\xf8\xec\xe5\xe6m/\xd9\ -\xd6\xa1\xe6|\xe7\xdd\xb2\xb31\xb3\x1f\x22\xd7\xb3\x02?\ -\xe8s\xa5\xfa\xb9\xff8\x09\x9a\xd6/N_\xacj\xaa\ -\xd3\xbd\xe8\xf9\xfao\xb3\xbc\xb3\xebf\xc2\x82f\xe6\x9b\ -\xbd\x00X\x1d\x85\x0f\x1fR\xb6\xd4`\xee/\x11\x1b\x13\ -*\xcb\x83\xf9gP\x17\x9f5\xb5H\x8d\x04\xa1a\xb3\ -\xf8\xcdo\xde/\xef\x1c?\xceT\xc7\x8d\xe6\xc7\xd7\xec\ -N\x1b\x99]\xfd\xbf\x1e0\xb7\x08\x00\xe3C\xb4\x06\x9e\ -\x10x\xf0\xf56\x8a\xd9\xc0\x22\xbc\xa3\xc1M\xd36P\ -z\xdf\x15?\xa4!mA\x05\x18k\x07\xa2\x85z\x01\ -\xda\x00\x87\xb6\x04e\x89A\xf4\x96,\x87a\xcd\xd1\x81\ -\xa5\xcb\xa9\xe3\x0cY\x18\x0b\x15V\x88\xd2ah\x0b\xe6\ -9\xc0P@I\xaa\xb3\x0c\x91\x16:?\xa1\xe3\xfe\xfb\ -\xd3\xed\x1a\xd8\x86\x92f\x1a\xcc\xd9!\x9f1Tc\x01\ -\xc0XV\xa0 \xccV\x17\x99P\xc2f\xc2F\x1d-\ -:\x9cF\xc85ld\xc8a\x1b\x0a-W\x1f\x001\ -\x19\x81\x8e\x0f\x00DFIG\x82\xc6E\x91\xb7QJ\ -\x01\x0eY\x00!\x0e\xcd\xa4\x81A\xa0\xad\x16z\xf5b\ -l#\xeaB\x8f'\xf4\xe7%\xf3b\xb0\x90d\xd9\xd7\ -\xbcH\xf0\xc1\x9dY\xfe\xcd>\xb6\x19\xc1\x99\x16\xd5\xcb\ -\xb1SW?K\x9c\xd9\xbe6.\x92\xeb\x09\xa33\xb4\ -n#b\xcb\xcc\x01\xf1\xa2\x14\x80:H\x07G\xd5v\ -\xb8hk\xd4`\x8c-l\x1eY\x88\x86m\x10\xc12\ -\xd2\xa4\x96(\x87\x1e\xeb\xa1Ln\x14YmB\xac\x96\ -\x1e\xfa v\xf3z\x1c\xc5j1\x8b^\xe9\xde\xd8\xde\ -e\xd1y\x1f\x94\xf9\x83K\xc3\xbe\xcd`V\xce\xc7C\ -o\x99\xa1e\xecM\x0c\x82d\x83D\x03Bv?\x8c\ -&\x9c\xcc\xb6\x8dd\xc0\xa8\x98\xb7t\x01\x8aC\x84h\ -\xacH\xd1E\xcd\xc0H&-\xe9\xc8\xe2\xda\xa1,.\ -\xb0#\xa9\xe4\x8a\x13\xd2i\xb2\x16\x89.>\x802\x8e\ -\x1aJ\xa2\xe2\x88T[\x00\xcdhhSjra\x1a\ -\xec \x03\x86H\xcc5\x06P\x97$\xae\xd1\x82(\x13\ -(H\xd3\xc8j\xa0\x22\xdd\x0a\xf0j\x98(9_4\ -9\x1cTG\x19f\x1e\x87\xd9\x00%\x8b\xa5\x0d\x12M\ -\x9c\xd3\xa2\x00\xa2d\xee\x080#\xd3Ad\xb3H\x88\ -\x09\xf9zN\xf6\x8e\xf17\xbcq\xfc'\x13\xa8\x0c8\ -\xe5@\xe3\xc9io\xfc\xf0|\xb2\x9f\x9d\xa1\x03\xff4\ -Y\xd3\xd5\x0fz\xd7\x0e8\xef\xb8\x168Y\xd5\xe8;\ -\x95 \xd6.S\x80\xd9\x000[y\x06\xb0\x1e&\x1f\ -D\xa7\x95\xcb\xee*m\xd8\x80t\x5c\xaa1\xbc\xb2:\ -V\xf6\x82u\xc6\x81G\x9d_\xd9\xaa\xc4USch\ -\x8e\xaez\xf9\xc2?\x17S\xb7W6p\xa7\xb2\xfe\x1e\ -\xcazd\x01C\x80\xf0&\x22]\xa9AY\x19\x95\x8d\ -\xf4\x01\xb1RvH\xbb6*[\xea\x80\xd6\x19\xa5\x1d\ -!\x96v%\xee\xde\x95\xe5\x9d\xca\xe6\xfb\x8c\xacr\x01\ -,^\xca\x06t\xd5tPVK\xa1R\x7fD\xea\xff\ -\xd5\xa8W\xa3\xb2^\xca\xd6\x19\xc3\xc8\x8a\x8f#\xab|\ -{e\xafQPw)\xe88S\xf0\xf2\x85\x8f.\x03\ -\x09m\x0c_z\x90\x1d\x05)\x0e\x00;\x98\xd1\xf4R\ -7w\xd05\x072\x1a\x0dV\x1e\xb1f\xa4\xc2\xa5u\ -\xf0\xf2u\xe7\xcd\xb5\xfe\x8f^.\xa4\x82\x1e8\xbe]\ -\x8da\xff\xec\xdd\xeb\xd5\xe3\xd5\xf7\xab\x1eJ\xea\x9b\x92\ -g\xa7\xffZm\xca\x81us\xfc8\xfb1~\xf7\xe3\ -\x06(\xea\xfa\xc7\xa0\x8f\x9f\xbd;?\xdf\xc6\xfe\xd9o\ -\xc4\xd4o\xc8\xd4\xeb\x845\xba;\xa9\xb8\x1d\x12TZ\ -\xa8\xe7\x1dq\xcey\xde\xfb\xad\x83d5#\xbd\xb06\ -\x90\xc3\x8a\x9c\xeb\x17%\xea\x17\xd7\xb3&$\xf3\xf9\x87\ -\xe0\xa7;\xcbP'\xfb=\x16\xea\xf70\xe1\xf9\x05+\ -\xd2\xfb\x9f \xdeG\x05\xff\xfb\x9a\xfeL>g\x9a\xb2\ -\xf7\x08N\x1e+\xbf\xad\xc9\xc5\xcd\xd3\x03\x22\xd7\x07{\ -\x10v\x953\xcd?\xa6\x13\x1f\xc3tb\x8f\x11\xa7\xcb\ -\x13\xc2q>\xf1\xfe\xf3\x87\xad\x99\x07\xa6\xf3\x94\xfd>\ -\xc2\x19\xc0\x95\x92\xcf\xac\xa0}\xebc\xa2\xc4\xc9Q>\ -\xaa\xfa\x7fYU\xe7[\xaa\xca\x93\xaa\x1fU\xf5\xbf\xac\ -j\xc0-U\xd5I\xd5_\xe1<`\x07\x13\x08x\x99\ -\x8ak\xd4\xf7\x07\xf3R|\x9e\x0fYn\xaa\xeb?\x86\ -r\xbcz?\x15\xe4\xc37C/\xfdp\xe2\xf5\xf1\xb3\ -\xd5\xeb\xf5\xb7E\x0f\xea\xd8\xae\xaa\xad\x06\x14\xfa\xa3\x9e\ -\xd5\x1a\x90\x99\xc5\xe1\x10\xf9\xa2\xccd\xbc\xc6\xceS\x1f\ -\xe2pf\x15E\x95\xa6\xaa\xc0\xc3\xe6(&!\xab\x87\ -4\x8b\x98\xc4\xa27\xad\xc0$(\x19\x13\xfeiN\xea\ -\xa4\xf65\x1c\x1b\xc7e\x8eK\xa2if\xda{1}\ -V_\x89\xdc\xa0\x03\xed\xaf_\xf5\xa7\xc7\xb2\xc1^\x1c\ -\xf7\xef8\x9f\x9d\x1d\xff4vmD\xaf/J\x95\xf9\ -\xc1|\xa2\x1d\xde\xa2R7\x8dQ\x02 %\xbd7\x8b\ -\xf8d\xad \x89n\x08X\x183\xa7\x8f\x95f5\xc3\ -+\x91R\xb5\x0c\xaa\xd5e\x904U@\x91C\xc3f\ -,H\xd9\xb1C\xa3\xe6!`<4\xcaK\xebY\x1b\ -\xa1&\xfb\xf6Y\xd6\xd84\x1c\xd7r\xbd!\xd5U\xc6\ -k\xa2Vs\x8c\xdd\x0e\x90\xc6\x1d\xb3\x06aX)\xac\ -1\x93\xb0\x0f\x18\x19\x90V\xc2v\x0c\x1fO\xd5\xe4\xa1\ -\x8f\x89\x0dA\xc1\xea\x8d\xd2\x92\x9c\xb4\x0a@\x000\xa1\ -\x8eqS\x0e\xc2\x83\xacFO\xf1\xf5\xa2\x84#\xe9\xd8\ -\xf0h`c\xa3\xcc\xd4m\xad|d\xa3L\xe8=p\ -m\x89\x1c^\x9c\x9a\xb9\x1a\x17\xe4\x9c\xae\x5c\x9d\xa7@\ -\x1aN\x14\xb0Q\xa1\xca?\x92\xccU\xa4\x80\x93K\xd4\ -\xf433Y\xa3.(\x86^YY\x0e\x03\x8a\x8f\xaa\ -\x88\xb8\xd98\x06,\xa3\xa0\xa2v8F\xb2\xeen5\ -\xa4\x0a\xb7I\x98\xe4\xa0\x1f\xf8\xc5E\xedPa\xe0a\ -\xbbs\x8a\x1b\xc2.\x948T\x1ax\x15\x9bT-l\ -\xe4\xbf\xb8\xa8\xb3.h\x92l\xc9\xbenp6\x8bM\ -\x03+\xb8\x8e\x94\x0b\xfaa\x8d\x02:*\xd5\xd0\xa8\x86\ -Q\xc7\xb8\x85G\x10\x96\x5c\x1fz\x84<\x8e~!\x00\ -\x1c\xce\x83Q:b\xd4\xe0\xa7:S\x87\xb0\xa5\x199\ -O\xb6\xbd#>\xb3b^\x80\xe77\xecT\xda[\xe8\ -\xec\x5c\xbb@4\xc8\xf1\xc0Cl\x88\x18H\xf2`\xdf\ -U0b\x83\x07\xef\x1dK\xca\xbb\xe7\x81$\xfe-\x01\ -d\x1e(\xcai\x1c$\xab\xd0\x0dI\x95u\xd5\x8d\x8a\ -\xeaC\x89\x8c\x9c\x96X\x96Qo\xd4\xc2X\xc41u\ -pM\x17\xc5\x0e%\xb9\x12\x0c\xb3S/\x87 \x1c\xa2\ -\x87\xb1\x00\x96\x9d\xa25#\xc8\xa4\x0a \xa2\xe8f\x07\ -\xa8c8\xe7\x8a#\x96 9\xc3\xa4\xec\x88\xc9\xeb\xec\ -\x09\xd5\xc6\x08\xa8\xbe-\xd1\x9aJD\xf8\xf6\xb5\xbd\xb1\ -\x99q\xb7\xcc\xa9\x9b\x95\xd7\xc1\x19ytj\x8f\xe0\xae\ -9\x124\x84\x90(2\x1c\x12p\x80(\x0dd\x98\x09\ -\x0a\x82\xf2p\xb2z@\x14AR\xfdA(4Z\x18\ -T%ZY\xcd\x85\xb4cuq\x8e5ff>\x04\ -*\x07\x8b\x9a\xe7Lh\x11\x9d@`34\xb4\x09\x02\ -H\xccP\xcf\x86\x12\x8e:C%Z(&\xe7\x1c\xad\ -X\xc7\xe84C\xd9[ \x88\xf8\x0cEj\x94\x1c\x94\ -\xa5W\xa1\x8eF\xe5nH\xce\xa6\x07\xa9M\xc1A\xab\ -fj\x00\x11\xaa\xa5\xbf&F\xd5\x9a\x0d\xd3\x82T\xc7\ -\xd9\xb5\xb99Z\x99H\x88\xa7QE\x03\x111+\xfb\ -\x920\xa2\xdcer?\x1f\xf4\x94e\xcd`\x88\x11\x84\ -\x8d$\x08J\xa7\x09\x15j\xa2\x9aZ:Y\xb4\x94\x0c\ -\x90-\xf4\xa8PJ7\xd0-\xd4\xbc\xb9\x93J\x14\x96\ -l\x9c\xe68\xa0\x8c\xa5\xa0qKd\xe3Z\x07g\ -lL\xaa4L\xe6\xdcI\x9c\x0e8\x9aJ\xa8\xc7\x10\ -\xc29\x01\xe4@\xac\x81&\x9a_`\x5c\xf546u\ -\x0a\xc2-\xd4\xb5%\x03a\xd6u\xc80C\xc7\xceh\ -\xb6\x84p\xe2\xdbE\x98H\xdf\x7f<\x02>\xda\xf1\x08\ -k\x8e\x95\xf3\x08\x1a{\x10nc\x12-D|\xf6>\ -\xcd\xc6\x86\x149\x96\xe2\x04L0\xce\xcb-\xc0\xa3\x06\ -\x03\x0c\xd5\x86\x94i\xa0\xc8| \xda\xdc(\xd1'\xac\ -\x06=\x9a1\x0a\xdb\x16\xca\x95\x03\xd3u\xc00\xc8{\ -'I\x1bE\xb8\xd6Hr*\xa0\xfc\x06\xec(\x11n\ -aG\xf4+\xf3k\x8a\x86D\x198\xd4O\xe6F^\ -c\x14\xe2$Y\x98eP\xd8\x01k3u-$\x89\ -\xc0\xa2\xc6\x92\x1aZ\x8ax\xa1\x0eb\xce%\xcf\x15(\ -\x0bbM\xe22\x0e\x19B\xe9p]!\x91X\xcfp\ -=\x0a\xa52\xc7t\xd0\x19*\xd9\x1c\x125g\xa8A\ -\x03%\x8f\xa1\x80\xa4&\x0cY\x86\x88\x8d\x9cI\xf1\xc0\ -\xbd\x05a \x95\xb93\xa1\x99\x0c\x13HL\x1c\xf5F\ -V\x87\xf5W4P!M\xea\x9d\x14\x0a)\x07\xc1\x0d\ -B\x98b\x90\x08FA{[R-j\xec&\xcb\x9b\ -\x00)p\x99.\x90\xa8\xea\x01gC'\xd5,\x0c\x01\ -\x1d\xfd@\xa0\xa5:\x04\x0f\x98\x92\xda\xe06\xd9\xea(\ -E\xa1\x90\x0eI\xa3DT\xb2\x01cp\xd3\xabW\xde\ -m\xd9y\xf1]\x88\xfaW7\x80\xf9\xfc\xff\x01\x9e\xd5\ -\xc9%:P\x01\x00\ -\x00\x00\x18\xd8\ -\x1f\ -\x8b\x08\x00\x00\x00\x00\x00\x00\x00\xed}ms\xdbH\x92\ -\xe6\xf7\xf9\x15:\xcf\x97q\x1c\x99\xca\xf7\xccrw\xcf\ -F\xdcL\xec\xc6F\xf4\xc6]\xec\xce\xc4~\xdc\x90%\ -\xda\xd6\x8e,)$\xb9m\xf7\xaf\xbf*\x90\x12X\x14\ --\xd0l\xda\xdd;c\xb8\xdd&\x1eTUV=\xf9\ -\x06\x14\x80\xc2\xf7\xff\xf4\xe1\xed\xc5\xd1O\x8b\x9b\xdb\xf3\ -\xab\xcb\x1f\x9e\x11\xe0\xb3\xa3\xc5\xe5\xe9\xd5\xd9\xf9\xe5\xeb\ -\x1f\x9e\xfd\xf5/\xff<\xcfgG\xb7w'\x97g'\ -\x17W\x97\x8b\x1f\x9e]^=\xfb\xa7?\xfe\xee\xfb\xff\ -5\x9f\x1f\xfd\xe9fqr\xb78;z\x7f~\xf7\xe6\ -\xe8_/\xffv{zr\xbd8\xfa\xc3\x9b\xbb\xbb\xeb\ -\x17\xc7\xc7\xef\xdf\xbf\x87\xf3\x15\x08W7\xaf\x8f\x9f\x1f\ -\xcd\xe7\xb5\xe6\xedO\xaf\x7fwttT\xc5^\xde\xbe\ -8;\xfd\xe1\xd9\xaa\xfc\xf5\xbb\x9b\x8b\xa1\xdc\xd9\xe9\xf1\ -\xe2b\xf1vqyw{L@\xc7\xcf\xc6\xe2\xa7c\ -\xf1\xd3&\xfc\xfc\xa7\xc5\xe9\xd5\xdb\xb7W\x97\xb7C\xcd\ -\xcb\xdb\xdf\xaf\x15\xbe9{UK\x8f\x9dy/C!\ -*\xa5\x1c#\x1f3\xcfk\x89\xf9\xed\xc7\xcb\xbb\x93\x0f\ -\xf3\xbej\xed\xe3\xb6\xaa\x8c\x88\xc7\xf5\xd8Xr\xb7R\ -/>\x5cT&>\xd9\x99\xe1\xe8\xba\xf4\xca\xfeu\xfd\ -\xfbP\xe1\x1e\x80\xdb\xabw7\xa7\x8bW\xb5\xe6\x02.\ -\x17w\xc7\x7f\xfe\xcb\x9f\x1f\x0e\xce\x11\xce\xee\xce\xd6\x9a\ -\xb9'\xbf\x93\xdbi\xe4\xf2\xe4\xed\xe2\xf6\xfa\xe4tq\ -{|\x8f\xb7\xfa\x9d94\xa0j\xf8\xec\xeeM\xdd\xe5\ -\x1cv\xdf,\xce_\xbf\xb9\x1b\xf7\xcf\xcf~xV\x07\ -\xcc\x8e>\xec\xdfw\xe9\xc5CC\x08\xc2\xc3\xa1{9\ -\xeb\x87t\xa3\xd6\xd9\xd5i\xedY\xed\xf7\xeb\xab\xf9\xe5\ -\xe2\xc3\xdd\xfc\xa7\xf3\xc5{\xa8\x02~\xee\x9b\xb8zw\ -w\xfd\xee\xee\xbfj\x89\xc5\xe5\xb2\xad:\xa8q\x84\xcb\ -\xc3C\xbd\x07\xb0o`\xf1\xe1\xfa\xea\xe6n\xfe\xea\xfc\ -b\xb1\x14x\xfc\xe6\xea\xed\xe2\xf8\xfa\xfc\xb2\x8e\xf0\xe6\ -\xaa\xfe8\xbd=\xbe\xfa\xf0\xf1\xf5\xe2\xf2\xb8\xd6\xb88\ -yy\xb18>9\xbd\xab\xb2n\x8fk\xe7.Nn\ -W\x9d\xbb\xbe|\xbd\xb5\xe9\x0fg\xd7U\x8d\x1e`[\ -\x0f\x7f\x1c\x0f\xff\xb1\x1e\xff\xfe\xed\xe2\xee\xe4\xec\xe4\xee\ -\xa4\xfe^\xd1z\x8fh\x0c%j\x99j\xb4/\xfe\xfd\ -\xcf\xff\xdc\xf6\x86\xfd\xd3\xd3\x17\xffyu\xf3\xb7\xba\xbb\ -\xdaZ\x81\x93\x97W\xef\xaa\x82\x86:\xabrg\xa7/\ -\xaa\xe1\xbc=\xb9\xfb\xe3\xf9\xdb\x93\xd7\x8bf\xa1\xff\xbb\ -\x1a\xca\xf7\xc7\xe3\x81\xae\xf0\xdd\xc7\xebE\x05\xbafo\ -\x16K\x0b\xdc\xe6\xb4\xf5\xbf\xb7\xe7\xad\xd2\xf1\x7f\xdc\x9d\ -_\x5c\xfck\x13\xf2\xec\xe8\xf8\xa1\x9f\xc7\xab\x8e6\xa0\ -\xed\x8e\xe3\xa8;\xf7\xc3\x1c\xf6\x1e\x0c\xa1)\xe5\xac\xf1\ -[\xd1u\xf6\xde\x9f_\x9e]\xbd\x9f\xdf\x9b\xa1G>\ -\xdb^\xe2\xden)c\xb3\xc4u\xed\xde\xed\x9b\x93Z\ -\xea\x87g\xbc\xed\xe0Uu\x8c:\x9ef\xa0\xb8:\xfe\ -\xfa\xdd\xf9\xd9\xe2\xee\xeabqsr\xd9H\xa0\xb5C\ -7U\xd4\xd6#W/\xff{qz\xb7\xfd\xd8\xcb\xab\ -\x9b\xb3\xc5\xcd\x83$\xda8pzuqu\xf3\xc3\xb3\ -\xdf\xfb\xb0\xad\x0e\xb5\xbe\xdd\x1fx5l\xcfFsy\ -yr\xbbX\xed\xde\xbe\xb9z_{U\xc1\xbb\x9bw\ -\x8b\xcd\x11\xfe|u\xf5\xb6\x0d\xcd%\xd1D\x1e\x11x\ -\xfa\xe1\x87gs\x12\x04\x16\x11\x7ft\xb4u\x96\x02\x9c\ -)?\xc5}m\x00?q\xa8\xd6V{\xdc\xe8\xbb\x9b\ -\x9b\x1a\xf4\xabW}\x5c\xdc\x8c\x01e\x1c\xcd@\xff\xed\ -\xf6\xf1\x0c\xc7\xe6/_^}X\x1d_\x99\xd9X\xa0\ -Rqo\xce\xd5J\xab\x1e\xaa_W\xac5\xf4@_\ -\x03X\x99\x1f\xc0\x9f\xceo\xcf\xab\xd3\x8f2\x87\xadF\ -\x8b\x0a\x9em\xa0-\x92\xd6\xacY;\xc0\xe0u\xbb\xfe\ -\xb0y\xe8\xe3\x96C\x8b\xb7\xd7\xab\xa3\xf5`\xf3\x97\xc1\ -\x1f\x1e\xbb\xc0\x80\x9f-^\xdd\x8e\xcan{\xec\x98\x8f\ -\xc6z]c\xeb\xf5\xe2\xb4\xa5\xc7\x95\x981\xb8.\xc7\ -\xde\x17\x95\x91\x851>_\xffW\xd3\xe0\xd1\x8b#\xd7\ -\xfa?\xdaZ\xe2\xe3\xb2\x04!\xb6\x7fpk\x99\x9f\x87\ -<\xb1\xad\x9d\xbe\x0b\xf3\xab\x9b\xf3\xd7\xe75\x88\x0f\xe5\ -tI\x94G\xab\xd3\xe9hmpZ\x1e\x02\xcc\xf75\ -\x87.Nn\xfe\xe5\xe6\xe4\xec\xbc\x1a\xd1z\x85\xfe\x88\ -h\xad\xf5\x10\x94n\xef\xae\xae\xdb\xef\xb1tCDm\ -\x94Y\xb9\xbb\xfbx\xb1X\x1e\x99\x0f\x9e\xf7\xe2\xf78\ -l\xdf\x0d\xd0\xca}_\xacW\xb9z\xf5\xeavQC\ -\x13\xd6\x0eN\x0b\x93\xcf\x17\x86[\x84\xd1\xc8\xc6q?\ -\xe8\xcf\xe5\x88\x8bMr\xc4%\x9e\xee\xb6\xcb\xa2\xbcz\ -u\x18\x8e\xb8\x94'\x85\xad\x22\xe1W\xe5\xc8q\x9a#\ -\xe7\xe9n\x1f\x8c#\xd7\xdf\x1cG\xc8\xd3\x1c\xe1D\xb7\ -O\xcf\x16\x8b\x83q\x84\xfe\xb4\xb03^\xe4\xe2\xec\x00\ -\x1c\xd5\x93\xca\xbb\xc5\xcd}\xc5\x96M\x11\x08\x19\x03\xc9\ -V\xed\x8d\xe7\xd5\xc0\xa8\xa48\xc6\x81\x8f\xcb\xf2\xac\xc5\ -\x1c\xe9\x1e\x1eO\xbc\x81\xb5\x14\xe2.0.%\x8ad\ -\x8c\x9c\xbfZ\xfc\xcb\xc9\xbb\xdb\xdb\xf3\x93\xcb\xffs\xf1\ -\xae\xf5f3\xf8\xd6a_,Nk\x93'\x17\xefO\ ->\xdev\xe4\x9c\xfdy\xf1\xd3\xf9\xc9\xdd\xfdY\xba\xba\ -\x99\x17z\xd6\xd3\xdaK\x10\xc9\xb2\xc6\xcc\xb2K\x9fi\ -5\xae\xa8SV\xe3:\xa5HLZ\xc8\xe2 V\xe3\ -\x8a\xf9\xb40\xc98[\xe8\x84\xb0Cz\x96\xd7m\x92\ -#\xf7\x89n\x97W\xa7\xf4\x8a\x0f\xe3Y\x94\x13Y\xcc\ -2\x16/m\x07a`;\xa8\xc4\x03'\xf4\x7f\xca'\ -\x96\xdb\xc4\xed\xef\xc8\xbd\xa3\xd1\x17r4scK\xce\ -\xa7\x1d\x8d>\xd7\xd1>\xd0\x0f\xcfB \x94]r\x0c\ -4\x15%\x03\x117\xd5\x07\xf4\x03\xd7\xb2\x09E\x8aH\ -\x19\xcbV\x94\x1c\xc20lD\x1f\x1b\xa7\xa9\x8c\xca\x19\ -&7^\xbc\xb9Y\xbc\xaa\xd7+\x8fR\xe8X\xee\xf5\ -\x0a\xfc\xeb\xe5\xf9]=\xc7\x7fw\xbb\xb8\xf9\x8f65\ -\xf1\x7f/\xffz\xbb~\x8e]\xa7}\xce\xfemq\xf7\ -\xe6\xaa\x8a\xad\x8d6N+\x0f\xd3#W\x08\xebG\xad\ -\xc0a\x99\x07\x1c5\xef6\xeab_k\xd4\x14\x05(\ -\x98\xb5\x1f9\x0b\x84\xa5\xf7#\xa7\x10(\x89A\xdd\xc8\ -\xdd@5\x89\xf8\xe9\x91\xfb\xc1G\xde\x97\xfaK\xbdv\ -\xbem\xb3\x14\xf5\x9a\xab\xfd\xbc\xa8\x13\x8f\x7f(1\xc3\ -\xe7;\xd1P\x1c\xd4S\xbd\xa3\xc1\x05H\xd2\xc8:\x1a\ -\x8c\x81)\xd5\xb3\xa7\xc1\x81Y\xd5\xe2\x09\x1a\xb8\x98\x1c\ -\xca\xec\xa7i\xa8\xf357\xe7\x1f\xfe@\x80\xec\xc5\xd1\ -gX\xff\x8c{sF`\xd4\x882\x9b\x13\xa4\x98\x87\ -\xf9Nde,\xb9\xeac\xc4\x1cA\x9c4\xd9\xbc\xb7\ -\x1ar\x08J\x8d\xde_\xd0A9\xcc\x9f\xa6\xcb\xff\x0e\ -\xe8\xd2\x80\xe2\xa9\xc1\xbd\x8b\xf9\xd2\xb6\xa2#K\x0b0\ -\xf5e+\x9a6pe\xe5i\xb2\xcand!\x7f\x15\ -\xb2\x0c\xeb&\x8d,/\xa2,\xb4FVk\xf0\xe4b\ -\x93\xac6\xa3DkCosH1Z\xc0Mu\xbd\ -q\xefU+\x9c\x09\x99\x142\xa2\xb5\x0a1\x81*\x93\ -tl\xf5\x22\xb9\xf8N\x9e\xe8u;\x18[\xf3Gt\ -\xa1\x9b\xfb\x8c\x99A\xc2)*Y\x0c\xea^\x16\xbe\xbd\xbb\xb9\xfa\ -\xdb\xe2\xc5e\xbd\x7f\xbd\xfa\xbd\xbc1\xf4\x02\x81$\xb4\ -\xd4\xed\x1eo\x1c\xd4\x81\xbe\xb8\xb9zwy\xb6\x0e\xfe\ -\xf7\xd5\xf9e\x8f\xd6\x9b]\x8b\x9b\x8b\xf3\xfa\xcf\x0b\xbd\ -\xc7\xceN\xea-\xa5\x9b\x9b\x93\x8f\x9d\xb0\x86\x0e\x97\x9b\ -\xb5$\x90\xad\xe0\xadW\xa5\x95\xb5\x7f;\x9a\x13\x13p\ -b\x99q\x013t\xe5\xa3?5\x94\xc1\xdc\x9cg\x9c\ -\xc0n\x85\x1b\xa6\xe0\xe2\x16\x1dfP\x98x\xbd\xfa\x8f\ -\x15\xd6{\x8b2\x06O\xe7\xb2\x84\x83\x81\x0a\xdb\xa6\xb0\ -\x10\xd0\x08\xa1\xf5\x86\xc3\xc04\xd9;\xcc!\x85Kn\ -\x08K\x01C\x97\x9980K\xf1e\xa3\xa9\x10\x85\x90\ -g\x12`\x98\x9e\xd2\x81\x05,\x8asY\xab\xaf\x08\xe9\ -*64\xea\x08\x12\xe86s\xed\x84d\x00\x89\xb9\xf5\ -22A\x19%h\x1d,\x08ZH(\xd6\xaa\x17\x82\ -\x08G\xc9\xd6\xe88\xa0\x92\xa0Z\xc4\xb25;\x8e\xbd\ -\x14\x08Q\xcc5\x8eFl\xa4s\xac\xde\x93\x1f\x06B\ -\x12\xd8\xe0QU\x9d\xb0Q\xabc\xc3\xa3\xf6Gl\xb4\ -\x93^\x16)8\x86\xe58\xb0\xa1Q\x12\x10\xe6\xf4\x91\ -\x83updkl`$\xb65+\x01\xa1\xc2R\xd9\ -\xef\xc4\x8c:\xea\xa4\x8c\xda\x1c\xc1Q\xef\xeb\xf5G\x1b\ -\xf9q\xab\xed\xff\xbc1\x13r\xbd\x8c)\xb16\xffq\ -\x1fU~\xcb!\x9d\x99@E\xedK\x84t\xf9\xea!\ -]\xcb\xde!}S\x91\x8f\x03 \x93\x80[Z\x1f\x93\ -\x98\x14\x8a\x0a\xaf\x07@\xa6\x00$\xa5\xe8\xb0\x04.\x99\ -\x1b\x01\x90u\xc5\x7f\x1f\x00\xd9\x15,\x90\xca\x8607\ -H\xd3B\xeb\x0d{@aK\xef\xb0\x02Lh\xb9!\ -,\x0c2\xa5\xf4\x01\x90#\x80<}=\x00\x8e\xe0\xe8\ -2c\xfd.\x00\xb21\x84&\xb7\x00\xd8\x09\xb9\xf7\xd3\ -^\xc6\xe8\xd2#8:\xffX\xbd\x0b\x80\xe3\x80\xba\x98\ -4\x8e}\x8c?#G#6\xd29V\xef\xc8\xef\x02\ -\xe0\xa8\xaaN\xd8\xa8\xd5\xb1\xe1Q\xfb#6\xdaI/\ -\x0b\x1dJ\x1am\x04@F\x83@\xd4.\x00\x8e\xe0\xc8\ -\xd6\xd8@\x17\x00Y\x10\x881[\x00\xec\xc4\x8c:\xea\ -\xa4\x8c\xda\x1c\xc1Q\xefc\xfd.\x00\x8ec\xea\x02\xe0\ -~\xa7B\xf2?\xeeT\xe8\xd3!\xbd\x0fe_\xfa\xea\ -c\xff\x0b\xa3'\xaf_8\xfe\xf1\xae_\x0a\x01R!\ -\xe4C$:\xca]\x13\xdd\xdf\x0f\x81\x99\x87$\xd0\xff\ -\xfe\x09\xecO\xb7\x0eL \x96\x7f<\x0b,zH\x02\ -\xe3\x1f\xd0\x02\xed\x90\x04\xd2\xae\x04~K\x22['\xb8\ -\xcbA\x93\xc8?\xe4%'\x97\xc2\xbb\x92\xf8m*\xf6\ -\x93$\xe6\x97\xb7\xc4\x03\xdeo\x9f+B\x98&\xcb&\ -\x13_KS\xdbI\xd4\xafi\x89*\xa0\xa1\xe1\xdc\x8d\ -\x8f\xd0\x80C\x95\xbb\xf1\xb9C&%Z7\xbc$\x10\ -\xb6\x08\xebF\xe9:\xf1\xb8\xc7\xf4\xed \x84 \x16\xcd\ -v\xe3\x8c\xd8\xd1\x92f\x08$j\xc1Z\x7f\x85\xa3:\ -\xc5,@\xdd\x8c\xd1f\x8c@\xa1$\x87\x08\xabR\xbe\ -\xe9\xe1\xf3\xf40'D0Q\xcdC*B\x88\xf2k\ -\xcf\x06\x12\xe9An\xf0\xf4s\x19\xfd\x98v\x9f\xcb \ -\x07-\x86\x88\xa9\xbf\xad\xfb:^\x00\x8b\x84\xcd\xd8\xc0\ -\xa5\xe0r\xa61\x08XRK\x19\xe0\x82%\xb3\x82\x02\ -\x1c\xe9(\x81\x15\x8d\x1e\xabe\xfbm3\x22\xdf\ -2\xc7\xfa\x0f\x91lK\x80\xbb'\x1d8\xd9\xf2WO\ -\xb6\x98\x07\xba\xf5F\x84\x8fr\x14\xb9\x03\xb3c\xf6I\ -\x8a\x11c9U\x952\xf6\x8a\xb1\ -\xdb\xf3\xf7\xf4{XQ\x8c\x90\x96o{\x8c{\xf3\x02\ -\x16\xe2\xe4^\x7f\x0a\x14\x09cy>\x91\xaawU\xf3\ -xR\xc1`V4\xbd?\xa9\xa0\x04R\x93\xec_\xd9\ -b@\xc50\x19O\x1f\xb6\x96=\xed\xca~:o\x17\ -7\xda\xc5\xa6\x8a\xab\xfcb\x17l\xe2\x1f7;\xf5\x96\ -}q\xb5mo\xb1O-\xc5\xb1e\xfd\x98\xefj\x0f\ -\xa7\xa5\xc5\x16i\xf4\xb44\xe7m\xd2\x0e\xb5\x16\x01[\ -\xe4\x14Kl\x89\x9f\xcfR\xc4~,\xb1%\x7f>K\ -\xa7\xf4%YrU\x9f^\xd5Bs\x1f[j\xdb\xe7\ -\xb3\xe4j8\xc1\xd2\x8e\xd2\xf0\x10,\xed\x9e\x1c&i\ -t\xe2}h|\xf9*q\x0f\x1a\x9dt\x82\xc6Ii\ -\x87\xa7Qh\x9a%\x91=\x5c\x12%p/\x96\xc4>\ -\x9f\xa5\x97\xafl\x81_\x90\xa5\xe9\xf0\xee{\x86w7\ -\xb7}X\xd2\xd8\xc7\x96\xce\xaa5})\x96\xd8\x1c{\ -\x96&:c\xe5\xb1\x81LP8\x06n\xe7\x8e\xa5)\ -iy\x86\xb8\x934\xda.M\x0fgK\xa1\xd3\xb6\x14\ -\xfe\xf9\xb6\xf4r\xcfS\x05\xf7\xc8}l\x89\xea\xf6\xa5\ -lIXbzA0)\xfbx\xdc\xe2e\xbe\xfc|\ -\x96\x84\x95~kI\x90K\xc1)\x96j\x99=r\xdc\ -\xe9\xb0\xed\xc8R/m\x9f\x1cG\xed\xcf\x97:\xa1\x12\ -d\x9a\xb4%\xe4=r\x1c\xe2.\x1e\xf7\xd9M \x90\ -\xb9\xb8\xb8~\xf7\x89e\xaf\xb6\x0e\xa0\xec`\xc4(\xb4\ -\xb5Igv\xa5\xca\xd3\x1e\x9d\xf5\x0c\x0e\x0e\xd9\xc5\x89\ -\x90'\x92\xfb\xeeb\xc5\x87?1e'\xdbW;\xdd\ -\xbeN(\x9b\xca\xde\xeb\x8eN\xafg:\xbd.\xea\xf4\ -\xea\xaa;\xae\xd3:q\x0d;\xb5\xdc\xd8\xf4$\xde\x13\ -Q\xc9\x83>o\xf6\xa0\x9f`\xea\x97\x13\x12\x82 \x0e\ -\xf5\xa9\x99\xab\x8a\x96\x04\xf2\xa0\xb0\x9d\xd6\xac!W\xb3\ -\x90\x875k\x8a\x13\xc5L\x1c0\x8bV|N\x90a\ -%\xb0<\xffU\xd9\xcc8\xe4ZX)\xa0\xc4e\x86\ -\xe0\x8ai61\x0d\xd4\xa9\x88\x09\xb0h)\xda\xa9\xa8\ -\xa2\x84T\xd1NCd@YH\xa4\xd3\x10\x07 F\ -\x9an>\xb8yPF\xeb\xf6\xd4\x03\xa8^\xf0\x90\x0b\ -\xd2\xb8y\x88\xd8\xca\x8e\x98\x95\x0b\xc6\x8c$\xc1]\xb2\ -\xcc\xe6\xa2@\x89Q>E\xf5\xf5\xc9Y7\xe1\xa5\x0c\ -\x81(\xc6\xfd\xe4\x18\x0aXb\xf7`\xf4XtD\xbb\ -\xa2[f\xe7\xbe\xac-\xa3>i\xcb\xc5\xf7\x88\x0cR\ - \x94\xca\xe6;\x95$`\x9a\xd2\xdd0\xd9V\xb6\xa2\ -s\x83\xc2b\x9c\xb1\x9b\x93\xa8\x83\xcf\x18\xc8\x16s\xfe\ -\xa2\xce/\x5c\xec)\xc2\x02e\x0f\xc2\xe6\x8eP\xd4\xd8\ -\xa8g\x0c\x0dR\xd0zG\x9d[\x01%c\xb5\x8e2\ -5p!\xc6\xf85c_\xa0\xed8\xfc\xfdoy}\ -\xa0\xc9\xdbl\x13\xa9\xa7\xbb}\xf7\xeb\xd2u\xa8T1\ -}c\xb5\xd1Fd\xf0\xf8\xa6n\x10`j\xc1\x9c\xb8\ -\x01\xdc\xdd+\xfeUY+\x87eM\x89\x0b\x0a\xb7\x14\ -KEL\xf3\x8b\xe5X\x81\xe2\x84\xdaS\x9a\xc0bE\ -\x7f]\xc7%\xfa\xc6\xe9\xc19\x95\xdf\x10\xa7V\x00\xc9\ -\xbd\xc4\xe6\x0a\xc1\x8e\x8aB\x1d\xa7.\xa0\xa6\x86}R\ -&\x02\xb1B\xfa\x84\xf3O\x9d}m.k7\xcf\xec\ -\x16\xb5\xdb\x89\xae\x03\x9c\xd8\xa8\xb0N\xac\x048.\x19\ -8\xb5\xb6`C\x8bB\xa3e\xcf\xfb\xc0[\x17I\xdc\ -\xb6F\xe2\x9c\x0c\xac_#\xf1\x80\xa4\x89jy\xe2<\ -\x5c\x85ib\xd5\xc9~\x89\xca~\x1d\xcb\x89\xb5.O\ -\xbb\xaaO\x98\xd8\x84\xbdO\xfa\x11WB\x0fI\x1a;\ ->ii\x94\x13+Nw\xabSO\xac\xdf\xdd\xad\xf5\ -=ep\xd3~X\x12I\xb1\xac\xfc\x10A\x0b\x06\x1d\ -\x8a\x9c\xe9+;\x15\xd2\xce\xa2\x8aA\xa0\x11\xd2\xc6z\ -\xb1\x08\x22\xa4\xfa\x895eO\xb7\xd6<\xedj\xfe\x92\ -\xa7t\x18\x0fl1\xc8O[\x0cn.#\xecAI\ -}\xde\xcb\x84\x10\xd1\xf2hubS\xce\xde`\xd2\xc1\ -4(\x7f\x19\x09\x04\xc9\xee\x143\x01\xd1t\xe1\xfc\x9a\ -^\xb4q\xfa\xee\x04\x94\x8e\xee='\x0clE\xc9\xbe\ -\x16)\xf3\x82P\xac\x98\xcd\xe6\xd1~\x09\x17\xf9\xaa\xac\ -ho)\x01\xa4\x18E:V\x86\xe4\x84a\x1b\xac\x98\ -B\xbaYt\xac\xb0\xe4r@\xbf\x88\x96\x12\x87s\x98\ -\xe9\x8bn\x11\xf6\x89\xa5\xed\xbbe\xf0'\x96\xcc\xef\x96\ -\xd7\xdf+\x0f}\xcd\x91\xf3\xbe\xc9\xa5\xffLB\xff9\ -\x85\xe9\x87\xf4\xd8Y\xf7y\x06\x8f\x9d}\xfa\x0b\x19\x87\ -~\xcey\xfb\xdc\x16m{l\xa7\xbf\xcf\xb2\xf7WV\ -\xfa\x9bl>\x84\x99)a`\xfb\x7fg\xa5\x17\xc78\ --\x8ev\xfc\xca\xca\xb40>\xd8\xedQ'\xdc_%\ -\xfd\xc7\x81\xa6U\xc2{s\xd4\x7f\x1ch'a\xba\xef\ -\xc7h\xd8\xd1\xa7\xbf\x12\xd5\x7fUj\xfa+T\xfdW\ -\xab\xf6sd\xcc\x1d\xbe)\xf5U\x1c\x19\xf7\xb7\x9a\xfe\ -Cd\xd3\x8a\xc4\x9d\xacf\xff\x0f\x91\xf5\xc2\xf4`\x9e\ -eE\x7f!G\xaf\xea\xb6\x13GV|\x0f\x8e\xa6\x85\ -\xe1vay8\x8e2\x7f!G.\x8b\xb2#G\xf8\ -59\xe2\x03r\xc4\xbf4B\x0f\xdb.\x1c\xa5\xee\xc1\ -\xd1\xb40\xdc.\xcc\x0f\xf8X\x82\xe6\xdf\xd1C\x09\x8d\ -\x90\xf6A\xda\xfak\xf5\xbe\xf9\xf8R\xb9\x04h\x12\xdb\ -\x8c\x18\xc8M\xd5\xeb{\xa5\x84\x06\xa4\x89>s\x02\xd2\ -\x88\xd4\xa3?U\xd4\x81\x84\xb4\x81i\x82\xc6\x03\x143\ -g\xc8\x92\xa8~\xbf\xdfJ\xaf~\x1a\x10R1]\xab\ -\xed@\xeaEs]\x8cC\x1a35\xe1\xe2\x10\xc3\xda\ -\x16D\x06\xc9&T[\x13\x067t\xa9`\x82\x9a\xc5\ -\x11;\xc4\xb0\x97\x9c\xca\xf7\xbbR\x1bX\xfe4(\xc3\ -V+/\x01\x06\xc2L\x97#\x11\x08F\xe3Y\x01U\ -\x0f\xb4\xdc\xc2B{\x89\xb5{#\x9f\x87\xe9\xb0\xed\xef\ -\xb4>\xfe\xde\xc8\xe6;\xad\xcb\xdd\x9bw\x17\x8b\x17\x8b\ -\x9f\x16\x97Wgg\xebo\xb9V-}\xd3\xcd/\xd0\ -\x8d\x98p\xaf\x9b\xf1\xa1%6\xf5$\xd7O.\xc6\xcc\ -\xc5\xf3 \xea\x0a\x86F\x0c\xa0\xb3\xd5\x91\x05\x812\x17\ -\xe2\x86\x11G\xe4Q H:\x96\xd2 \x95\xc2\xe9G\ -^Z-\xae\xdc\x04A6j\x0b8[\xddO\x9c\x91\ -\x83z\xc6\xc3^m\xb6\xfd+\x80mH\xb5A\x07t\ -\xc2\xa8\x0d\x22\xa4\xba{\xdf\x8b\x1f\xbb\xdd\x07\xde\xee\xb6\ -=T\x91B\x85\xa2\x0c\xf3\x8a\xe3^Q #,:\ -c\x10\x13J\xe3\xe7\x9b\xecs\xf0n\x9ea*\xf8y\ -T\x7f\xb7<\xa1\x5c66~E\xf5\xf9\xaf\xac\x02P\ -TEn&\x19E$*\xed\x01\x5cH]f\x05\x8a\ -\x17+Y\xf6WEh\xbaR\x0e\xaa\xa8\xbb\x05\x91J\ -\xce\xd2!-B\xb5\x8auH\x14\x8bG\xaa\x90B\x9f\ -r\x84@%*\x19\xdf=\xa1\x1d>\x90v~\xd7\xe7\ -\xa6Zo\xd1\xf2S\x9dA9]n\x9f\x8ax\x06E\ -\x08\xb9\xb4\x18\x91\xc4Tt\x08y\x04\x81\xec\xa5E3\ -,\x5c\xdc\x86 &@li3\x170rb\xaa\xd8\ -*\xdc\xcd\x5c!\x5c9c\xa8\x9d\x18Yk\x1b\xa4\xa9\ -J\xb6H\x16`,\xadM\x22\x84\xe2:hV\x04\x94\ -L9*\xaaCH,G\x5c@0,u\xc0\x04Q\ -m\xc4\xd6j\xff\xb8\x8e\x06\x94BDC\x84\x1bQ\x03\ -\xf2\x12\xc6-d\x9a\x9aZ4_N\x0b\x13\xde>\xf6\ -m\xe8\xe3\xf0\xa7\xa1\xbd\xd6\x9f\x88ur\x90X\xc7\x91\ -`\x86\xaa3&P\xd7\xa1\xaf\xdc8C\xd3\x98\x85@\ -\xa2\x17\x1b\xc6O\x0cA\xc5+j\xa0\xe4\x864`E\ -\xc9f\xe1\x90\x9c$1TF\x14\x5c\xaej\xe2\x84N\ -\xad\xc9h\xd9\xa1\x00F\xba\xb7\xc6\x92*e\x0c\xee\x98\ -u\xc7\x86\x9d4\xca2\xec\x8dE\x7f\x1c\xf6Y\xa0p\ -a\xe1V\xb5\xed#\x84\xa4X+M\x90\x1a$\xcd\xd9\ -\xd9\x94S\xb6\x0ej\x0b8\xfa\xee\xd4\x87\xe6\x102\x95\ -J\xe1\x990\x83\xa2\xb1\xcd\xe6\x02\xe4\x81\x99\xf6\xfc\xb1\ -\x1a\xe9\xb1\x1a\xd7\x17\xa6\xf8\x84\xd7\xfa\xf3\x8de2\x00\ -#\x02K|\xd1\x0f\x06\xf4\x16\xb4\x19\x06\xe8\xd3AZ\ -\x18\xc4\x5c\xa2R\xaf\xcd[\x15\xb3\xb9\x1e\x82%1W\ -\xd4\x00M\x1cc\xddu\x1c\x98\xac\x88m8Y\x14.\ -\x9d\xe7\xa9\x80\x16N\xe5\x0eu\x87b\xac!U\x90\x09\ -\x18\x12F6\x94\x22\xbc\x96\x0d\x03'v\xd1\x16a\xb8\ -8\x16;*\x02\xe2\xca63\x07\x16\xb1\xe0\x95;R\ -\xba\x0e\xbd\x8c\xe2\xcb\x93\x18Qp-h\xdaPt\x1d\ -\xe2\x08\x93\xba\x0eCD\xe3,[\x87\xfd\xe3Vt\xd3\ -\xbf\x99\x85w=\xf5\xb4r\x10\xff\xee\xbb\xe5.\xcb\xf0\ -H\xa0V\xdc\x07\xb4\xa8\x06Y\xd3[\x90\x09S\x1b;\ -gA\x1b0\x0a\xc5\x94\x86%\x17,\xbc\x0c\x84\x85(\ -\xc4\x9a6\xb1`\x9a6\xcc\xc3E\x07L\xc2\x82\xaca\ -&m\xcc\x0d\x0b\x0eOY\xd66N]\xa1\xe2N\xb1\ -,iE\x06\xc8027*[\x89\xb0eeu%\ -\xd2\x86\x16!v\x1e0\xcc\xa1\x93\x01D\x9aKL\x82\ -\xc2K\xc3\x04M4V\x11\xdcK\x90\x0f(\x99k.\ -KJ\xf0\x00q`\xe4Fe\x09\xd5U\xdd\x8aR\xc3\ -\x8c\x0b\x97\xd2[pH\xf8\x06T4\xa8\xe4C\xe6\xd0\ -\xc2\x03j\x89.\x1b%=%t\xca%\xa8\x00\xa2\xb3\ -\xd1\xa6\xa3\xb08!\xafP)\x1e\xd1\xa1\x82\xa0\xce\x85\ -\xac5\x9b\x94\xc9+?MUod\x18hJ\xb27\ -sO\x0b'i\x98%K\xe6\xba\xa78T\xa8X\xeb\ -Ua`%$\x9e\x99Aj\xaaSm3\x12\xc4\xd0\ -\x9cf\x8ePP\x92\xf4\xc8\x11(\xd2\x88[*7J\ -B?Rjn\x8c\xe2\x83\xeb&\xb35\xcbp\x84\xd0\ -b*\xad\xa4\x17\xb7\xe5\xe9X\x11\x1b0\x02\xe1\x94\xb0\ -&[$8K\xf3hcV\xd4e\x8f\xc4\x8b\x8a\x0e\ -\xa8\xa9\x96\xaa\xf2\x86zI\x13i\xa8\xaa\x9a\x97\x16\x10\ -Z\xd78\x1b&\xaa\xc5}\x15$\x04\xa9\x8b\x12\x0d\x15\ -&\xcb\x01E\xd2hcw\x06\x0cE\xb5\x99(P:\ -\xd2\x93\x11%\x85)\xe8\xfe\xa4\x81\xa9\xb1\x1c\x19\x8c1\ -x\x1fFX\xc5\x18R\x22\x9c\xbbX\xdaP\xc7\x94\x18\ -\xd0,\x91<`\x91\x962`\xa1\x1a\x0d*HE\xbd\ -w\xf2e\xba.\xbcD\x991\x1bf\x82\x1a:`F\ -X|k\x88\xf8q\x1b\xba\xe5|\xc5w\x8fg\xfe-\ -\x9e}\x8bg\xdf\xe2\xd9\xb7x\xf6\x1b\x8eg\xc6\xbas\ -<\xdb\xe7\xfa\xeb\xfb\xe3\xdb\x9f^\xff\xf1w\xff\x1f\x87\ -I\xee\xb2\x11\x94\x00\x00\ -\x00\x00\x19\x91\ -\x1f\ -\x8b\x08\x00\x00\x00\x00\x00\x00\x00\xed}mo\x1c9\x92\ -\xe6\xf7\xf9\x15:\xcf\x971\xae*\x14\xef\x11\xf4t\xcf\ -\x027\x83],\xd0\x8b;\xdc\xce\xe0>.d\xa9d\ -kG\x96\x04In\xdb\xf3\xeb\x97\x99%\x89E)[\ -Y\x96J\xee\xde\xeb.\xb7\xdb\x95O2\x18\x8c\x87\xc1\ -\x88L2\x8b\xf9\xdd?}\xfep\xba\xf7\xe3\xea\xf2\xea\ -\xe4\xfc\xec\xfbW\x04\xf8jouvx~tr\xf6\ -\xee\xfbW\x7f\xfb\xeb?/\xf3\xd5\xde\xd5\xf5\xc1\xd9\xd1\ -\xc1\xe9\xf9\xd9\xea\xfbWg\xe7\xaf\xfe\xe9O\xbf\xfb\xee\ -\x7f,\x97{\x7f\xbe\x5c\x1d\x5c\xaf\x8e\xf6>\x9d\x5c\xbf\ -\xdf\xfb\xd7\xb3\xbf_\x1d\x1e\x5c\xac\xf6\xfe\xf0\xfe\xfa\xfa\ -\xe2\xcd\xfe\xfe\xa7O\x9f\xe0\xe4\x06\x84\xf3\xcbw\xfb\xaf\ -\xf7\x96\xcb*y\xf5\xe3\xbb\xdf\xed\xed\xedU\xb5gW\ -o\x8e\x0e\xbf\x7fuS\xfe\xe2\xe3\xe5\xe9X\xee\xe8p\ -\x7fu\xba\xfa\xb0:\xbb\xbe\xda'\xa0\xfdW\xad\xf8a\ -+~8(?\xf9qux\xfe\xe1\xc3\xf9\xd9\xd5(\ -yv\xf5\xfb\x8d\xc2\x97G\xc7\xb5tk\xcc'\x19\x0b\ -Q)e\x1fy\x9fyYK,\xaf\xbe\x9c]\x1f|\ -^\xf6\xa2\xb5\x8dS\xa2\x8c\x88\xfb\xf5\x5c+\xb9]\xa9\ -7\x9fO+\x13?\xd9\x98\xf1\xec\xa6\xf6\xca\xfeE\xfd\ -{'p\x0b\xc0\xd5\xf9\xc7\xcb\xc3\xd5q\x95\x5c\xc1\xd9\ -\xeaz\xff/\x7f\xfd\xcb\xdd\xc9%\xc2\xd1\xf5\xd1F5\ -\xb7\xe4wz\xbb\x1e9;\xf8\xb0\xba\xba88\x5c]\ -\xed\xdf\xe2\x83|\xef\x0e\x15\xa8=|t\xfd\xbe\x1er\ -\x8e\x87\xefW'\xef\xde_\xb7\xe3\x93\xa3\xef_U\x83\ -\xd9\xd1\xc7\xe3\xdb&\xbd\xb9\xab\x08Ax\x9c\x0cB\xfb\xff~}\ -rz\xfa\xaf\x83\x92W{\xfbw\xed\xdc\xbfih\x05\ -\xc6\xc3fG=\xb85s<\xbas\x88\xa1c\x8e\x06\ -~+\xba\xc9\xde\xa7\x93\xb3\xa3\xf3O\xcb[w\xf4\xc8\ -W\xd3%n\xfd\x972\xee\x97\xb8\xa8\xcd\xbbz\x7fP\ -K}\xff\x8a\xa7N\x9e\xd7\x01R\xed\x19\x1c\x15o\xce\ -\xbf\xfbxr\xb4\xba>?]]\x1e\x9c\x0d$\xd0\xc6\ -\xa9\xcb\xaaj\xf2\xcc\xf9\xdb\xff\x5c\x1d^O\x9f{{\ -~y\xb4\xba\xbc\xd3D\xf7N\x1c\x9e\x9f\x9e_~\xff\ -\xea\xf7>~nN\x0dm\xbb=q<~^5w\ -y{p\xb5\xba9\xbcz\x7f\xfe\xa9\xb6\xaa\x82\xd7\x97\ -\x1fW\xf7-\xfc\xc7\xf9\xf9\x87Aa\xa2z\x86\xdd?\ -}\xf8\xf9\xfbWKc\x08\xe7\xd0\xf2\xe0lmk\x09\ - \x8e\x92\xf1\x13\xd4\xd7\x0a\xf0'NUi5\x7fP\ -\xe9\xc7\xcb\xcb\x1a\xfb\x97\xa7\x07_V\x97-\xae\xdc\xb8\ -\xcb]\xb1\xc1\xa4[\xb7\xac\xdeV\xf9\xacc\xb4bC\ -uw4\x0c\x00+\xf3\x1d\xf8\xe3\xc9\xd5I\x1d\xc0\x8d\ -\x8b\xf1SG~\x05\x8f\xee\xa1Cd\xacY\xb0\x1a\xc0\ -0\xf0~\xf1\xf9\xfe\xa9/\x13\xa7V\x1f.n\xce\xd6\ -\x93\xd5\xef\xd7~\xfd\xd0\x95G\xfchu|\xd5:m\ -8b\xc7|`\xebE\x8d\x95\x17\xab\xc3!\xdd\xdd\xa8\ -i\xc1rm{_T\x1a\x0b-\xde^\xfc\xc7\xd0\x15\ -{o\xf6\x5c\xeb\xffh\xb2\xc4\x97u\x09B\x1c\xfe\xc1\ -\xc92\xff\x18\xe3\xfeT=}\x13\x96\xe7\x97'\xefN\ -j@\x1e\xcb\xe9\x9a(\x8f^\xa6Z\xbda\x9c\x96\xbb\ -@\xf1]\xcd\x89\xab\x83\xcb\x7f\xb9<8:\xa9\xde\xb0\ -)\xd0\x9f\x11\xadRw\xc1\xe5\xea\xfa\xfcb\xf8\xdeJ\ -\x0f\x88\xa85\x9d\x95\xbb\xeb/\xa7\xab\xf5\x99\xe58\x82\ -\xde\xfc\x1e\xc7\xcf\x1fG\xe8f\x18\xbe\xd9\x149?>\ -\xbeZ\xd5\x10\x83\xb5\x81\xf3\xca\xe4\xeb\x95\xe1\x842j\ -l\xec\xf7F\x7f-G\x5cl\x96#.\xf1x\xb3]\ -V\xe5\xf8x7\x1cq)\x8f+[G\xb4o\xca\x91\ -\xe3\xc1\xed2\x09\x97\x82_A\xe2\xe1\xe9\xc9\xc5\xff\ -9\xb8~\xbf\xa9\xf8\x16\x13\xb46\xa0n\xc1\xc9v\xb7\ -\xfb\xa9\x8bVW\xbb\xbf\xbb\xbb\x8f\xfb\xe3q])z\ -S\xd7\x90\xfe\xf0\xfb\x87\xd6\xbd\x1e\xcf.[\xe1\xab\xeb\ -\xcb\xf3\xbf\xaf\xde\x9c\xd5\xf5\xe8\x9b\xef\xeb\x05\x9e7\x08\ -$\xa1\xa5~n\xf1\x81\x83j\xe8\x9b\xcb\xf3\x8fgG\ -\x9b\xe0\x7f\x9e\x9f\x9c\xf5h]\xb4Z]\x9e\x9e\xd4\x7f\ -\xde\xe8-vtP\x97\x86./\x0f\xbet\xca\x06t\ -\xbc\xdd\xac%\x81\xec\x06\x9e\xbc+\xad\xac\xfd\xdb\xde\x92\ -\x98\x80\x13\xcb\x82\x0b\x98\xa1+\xef\xfdy@\x19\xcc\xcd\ -y\xc1\x09\xecVx\xc0\x14\x5c\xdc\xa2\xc3\x0c\x0a\x13o\ -\x8a\xffPa\xbd\xf5(c\xf0t.k8\x18\xa8\xb0\ -\xddW\x16\x02\x1a!\xb4Yq\x18\x98&{\x879\xa4\ -p\xc9{\xcaR\xc0\xd0e!\x0e\xccR|]i*\ -D!\xe4\x85\x04\x18\xa6\xa7t`\x01\x8b\xe2\x5c6\xe4\ -\x15!]\xc5\xc6J\x1dA\x02\xdd\x16\xae\x9d\x92\x0c \ -1\xb7^G&(\xa3\x04m\x82\x05A\x0b\x09\xc5\x86\ -x!\x88p\x94\x1c*m\x06\x95*\xafE,\x87j\ -\x9b\xed\xa5@\x88bnp\xd4\xb0Fg\x13\xef\xc9\x0f\ -\x03!\x09\x1c\xe0\xd6U\x9d\xb2\xd6\xab\xad\xe2\xd6\xfb\x0d\ -k~\xd2\xeb\xa2*\x8da\xd9\x0c\x1b+%\x01aN\ -o\x1cl\x82\x8d\xadVA#v\xa8V\x02B\x85\xa5\ -\xb2\xdf\xa9i}\xd4ii\xbd\xd9\xc0\xd6\xef\x9b\xf2\xcd\ -G~\x98\xf4\xfd\x7f\xdc\x9b\x09\xb9X\xc7\x94\xd8\x98\xff\ -\xb8\x8d*\xbf\xe4\x90\xceL\xa0\xa2\xf6\x12!]\xbey\ -H\xd7\xf2\x8c\x90\xdew\xe4\xc3\x00\xc8$\xe0\x96\xd6\xc7\ -$&\x85\xa2\xc2\x9b\x01\x90)\x00I):,\x81K\ -\xe6\xbd\x00\xc8z\xc3\x7f\x1f\x00\xd9\x15,\x90\xca=e\ -n\x90\xa6\x856+\xf6\x80\xc2\x96\xdea\x05\x98\xd0\xf2\ -\x9e\xb2\xa8\xe2)\xa5\x0f\x80\x1c\x01\xe4\xe9\x9b\x01\xb0\x81\ -m\xc84\xf9.\x00\xb21\x84&\xaf\x03`+\xd4\xc6\ -i\xa7\xa3\x0d\xe9\x06\xb6\xc1\xdf\xc4\xbb\x00\xd8\x0c\xeab\ -R\xb3\xbd\xc5\x9f\xc6Q\xc3\x1a\x9dM\xbc#\xbf\x0b\x80\ -\xad\xab:e\xadW[\xc5\xad\xf7\x1b\xd6\xfc\xa4\xd7\x85\ -\x0e%\x8d\xfa\x00Xa\x83@\xd4.\x006\xb0\xb1\xd5\ -*\xe8\x02 \x0b\x021\xe6\x10\x00;5\xad\x8f:-\ -\xad7\x1b\xd8\xfa\xbd\xc9w\x01\xb0\xd9\xd4\x05\xc0\xa7]\ -\x0a\xc9\x7f\xbbK\xa1\x9f\x0e\xe9}({\xb1\xbb\x8f]\ -\xdd\x18M\xdf\xbfp\xfc\xfa\xee_\x0a\x01R!\xe4]\ -$:\xca]'\xba_>\x81\x99\xbb$\xd0\x7f\x1d\x04\ -\xb6\xcb\xad\x1d\x13\x88\xe5\xd7\xe7\x81EwI`\xfc\x0a\ -=\xd0vI mK\xe0oIdr\x82\xbb\xec4\ -\x89\xfc*o9\xb9\x14\xde\x96\xc4\xdf\xa6b\x7f\x92\xc4\ -|yO\xdc\xe1z\xfbR\x11\xc24Y\xa6\x99x\xf9\ -\x9e\x9a&Q\xbf\xa5'\xaa\x80\x86\x86sg\x1f\xa1\x01\ -\x87*w\xf6\xb9C&%Zg^\x12\x08[\x84u\ -V\xba\xce<\xee1\xbf\x1c\x84\x10\xc4\xa29,\x9c\x11\ -;Z\xd2\x02\x81D-X\xeb\xb7pT\xa7X\x04\xa8\ -\x9b1\xda\x82\x11(\x94d\x17aU\xcao\xfd\xf0u\ -\xfd\xb0$D0Q\xcd]v\x84\x10\xe5\xb7\x9e\x0d$\ -\xd2\x9d,\xf0\xf4s\x19\xbdM\xdb\xcfe\x90\x83\x16C\ -\xc4\xd4_\xd6\xba\x8e\x17\xc0\x22a\x0b6p)\xb8\x9e\ -i\x0c\x02\x96\xd4RF\xb8`\xc9\xac\xa0\x00G:/\ -\xd8\xc1\xc2M\xacb\x0a\x91\xc4U<\x810\xa2\x8c\xe2\ -\x85 \xd0\x8a\xe4B\x0dPX\xc3\x1aX\x16\xc1\x10j\ -\x8eeC>\x0bx \x17\x1f\xe4- \xcd-xA\ -\xe8`EK-*\x08\xa4\xa2R\xee\xc0\xda&\x12`\ -\xca\xe8\xe5e\xad\xe8NO\xde |\xd7\x1co\x92}\ -\xcb\xc9A\xd5\xc9u\xc1\x0a\x99\x051\x86\xe9/ps\ -&\x1b\xc0B\xe4\x9c\x15T\xb8\x95\xbe\xa3\x8ds$3\ -m!\x04\xe2\xae\xe6#H\xc8J\xba\x905o\x18w\ -\xd2R \x8a\xf1\x8dn\x05v\x22\xd1\x85!(\x16r\ -k -<*\x88\xe4Mw$ )\xea\xc8\x99\x18\ -h\xd6\xcfxo\xe9\x1c\x85\xabnc0\x22\x92[\x8c\ -*\xe6\x02\xc4\xf7\x84C $3\xca\x9d\x96h`\xde\ -\xb5'n\xa5\xfb\x86;\x82\x17\xb2B\x0b\xa9\xd2\xecQ\ -\xa8\xaa.P\x9c\x8a\xf1B\x04\xa2\x14\xc1A\x9a\x80s\ -\x94\xaf\x22\x81\xb7\xf2\x0c\x8ehC\xcc\x090%\x0e\xaf\ -\xa0\x81\x97B\x12\x83\x0b\x9a!\x9bNz\xeb?\xf6\xaa\ -\x17\xab\x8e\xadb\x07\xb1\xdb\xae\xd4\x046M\x8e\x0a\x8f\ -\xad\x22\xa9\xad\x220\xa3\xc8\x0a\x16H&u[\x83C\ -+\x8b\x91\x8dS\xe5k\xc4\xed\x16\xf9\xf3\x1a\x91\xe4j\ -\x8e'\xb8\xb9\x10U\x10\x81(4i\x11\x04\x94\x14\xaa\ -Uo\xdc\xb8t0\x98\xder\xa4\x06\xaa\x86\xbc\x08\x81\ -\x82\x99\x92\x15c0\xd3\x92w\xe0 \x8dk\xb7\xec\xa5\ -% \x93\x8d\xe2N\x11W\xd0A}P\xbf\xd9$\xf1\ -\xae\xe5?\xdc \x22\xcd\x96Q0\xd4R\x067-\xac\ -\xca1\xaa\xe0d\xa1\x81\x98\x12\x96j\x15K\x88\x9b1\ -\xb2i\x0a\x02\x99Q\xe1\xa1\xbb\x884nl\xc1\x92\x88\ ->v\x02+\x1aMt\xcb\xe4\xb2\x19\x91O\xcc\xb1\xfe\ -*\x92m\x09p\xf7\xa4\x1d'[\xfe\xe6\xc9\x16sG\ -KoD\xf8 G\x91;0;f\x9f\xa4\xc8\x13$\ -0y3IQ (a\xb1\xcd,EAPH,\ -\xef\x05\xfbLHI\xf6\x96\xa66\xc1\x96\xa6Z\x05}\ -\x9e\x22S(\x99e3M\x11\x07pXx\x97\xa6\x08\ -A\x8c\xf0\x9e|\x11Pv)\xd1R\xd5-\xa8\x18-\ -[\xb5\x0a\xee[@\x02\x16\xa9e3]\x11%D\xa6\ -\xe9f\xba\x22f@\x0c\x91>c\x11\xdb@-Q\xcb\ -X7\xa0ht\x19\xabU\xd0'-\xaarE\xd4[\ -\xd2\xda\x04[\xd2j\x15\xf4\xa9\x87\x84\xc1\x99\x986\xf3\ -\x16i\x01WQ\xdfL\x5c\xe4\x08l\x0f+\x08\x84\x0c\ -\xe2\x96\xba\x1a\xd62W\x13\xbfo\x80\x05$K\xe1\x96\ -\xbc\xd6 \x16\xe5\xdcL^d\x09Jb\xd9g/\xb2\ -\x02!\x9c\xb1\x99\xbd\xc8\x19RH\xbde\xaf\xce\x8f\xfb\ -\xf4EJ\xeb\xa6\xf5\x19\x8c\xd4@2\xc863\x18i\ -\x82\xa7\x12\xb5\x0cv\x03\x0a\x8bti\xac\xc1}.\x1b\ -\xe1@\xeb\x12\x07i\x00k\xe9s\x19\xa9\xae\xfd\xbeO\ -G\xa4\xf5(Bl3\x9b\x91\x14\xf0\x0c\xa1\x96\xcd*\ -\x167n\xdb\xcb\x8b\x01\x92\xa6n\xa63\x12\x01+\x8a\ -\xd1\xb5J\xa4Y\xd0\x0ckp\x97\xd8F\x18\xbd\xd0f\ -b#QPR\xd7\x96\xd9\xd6-\xc8\x9b\xf1\xd45,\ -\x803\xacKm\x83a,(\xd2R[\xd7e]v\ -\xdb\xfe\xee\x81\xff\xfb\xdd=L\xa4\xe9\xdf\xee\x89\x9f>\ -i\x8e\xbbL\xd3s?\x0d\xe3\xa4\xd9jf~;6\ -\xfd\xab/Ny\xe4\x97c\xc2\x19\x9c\x91\xad\xc8\x8c\x11\ -\xf3\xbf\x1dk\xab\xe1L\x80B\xdc\xffZF\x08$\xdc\ -#\xfb_\xd6 \xb8k\x1aw\xab\xe3\x85!+(\xba\ -\xe5\xb3\xc3\xc0\x22\xcc\x5c\x16\x04\x85\x0c\x8b\xd2\x0e\x1e!\ -f\xb6\xed\x1e\xd3V\xdf\xc6\x11fYS\x07\xe6L+\ -\x1dmV*j\x89\xd1\xd1\x16\x90%\x84):\xdaZ\ -\xd9mis\x16\xaf\xb4\xed\x84.,\xdf\x94.\xa2\x00\ -a\xf1\xdc\x82.F\xe0\x94\x0c\x9a\xa6\xeb\xd9\xc6\xbb[\ -le\xbc\x13>\xcf\xf8\xf9\x07Y\x08\xdc\xc8\x89K\xed\ -\xd3_.]\xb63\xba\xe6c\xac\xab\xe5\x8b\xc4XW\ -\xc7Gb,\xa7\xaaR\xc63bl\x9f\x9b\xb6\xf9\x1d\ -V\x14#\xa4\xf5\xaf=\xda\xd1\xb2\x80\x858\xb9\xd7\xaf\ -\x02E\xc2X^\xcf\xa4\xeam\xbb\xb9]T0\x98\x15\ -M\xef/*(\x81\xd4$\xfb\x9fl1\xa0b\x98\xb4\ -\xcb\x87\xc9\xb2\x87\x13e\xa7\xf2vq\xdbj\xb1\xbb\xb8\ -\xca3\x86`S?Q\xed\xcc/\xd1\x8b\xabM\xfd\x8a\ -}n+\x8e\x89\xfdc\xfeX[8\xaf-&\xb4\xd1\ -\xe3\xda\x9c\xa7\xb4\xedj/\x02\xb6\xc89\x96\xd8\x12\xbf\ -\x9e\xa5\x88\xa7\xb1\xc4\x96\xfc\xf5,\x1d\xd2K\xb2\xe4\xaa\ ->\xbf\xab\x85\xe6\x13|i\xfc|=K\xae\x863,\ -m\xa9\x0dw\xc1\xd2\xf6\xc9a\x96F'~\x0a\x8do\ -\x8f\x13\x9f@\xa3\x93\xce\xd08\xabm\xf74\x0a\xcd\xb3\ -$\xf2\x84!\x89\x12\xf8$\x96\xc4\xbe\x9e\xa5\xb7\xc7\xb6\ -\xc2\x17di>\xbc\xfb\x13\xc3\xbb\x9b\xdbSX\xd2x\ -\x8a/\x1dUoz)\x96\xd8\x1c{\x96f\x1ac\xe5\ -\xa1\x83\xccP\xd8\x02\xb7s\xc7\xd2\x9c\xb6\xe5!\xd8\x91/8\ -\xc6Q\x10\x91\x17\xcbR\x00\xb9Hz\xe5\x92\xc1]\x0b\ -\xf9\xeb\xe9Y\xc2\x86\xce\xcfEN\xcf[\xf63\x94?\ -\xeb\x88\xcf\x9d\xae\x92\x92A)\xe18\xbe\xbdI\xa5\xa0\ -\x95\xb9\xd5\xa7\x17zA\xe5\x8b9\xec\xfc\x0a\x08{\xe1\ --I\xdd\xe5\xa2\x0f\x0b\x98R\xaa/\xb4\x80\x93\x9b4\ -\x07\xde\xf6\x8db\xdb\xbfC\xac_\xb5\xf9y\x1f?z\ -rx\x98_y,\x0e\x88,\xc5o\x9e\x1bd\xb5\xa8\ -\xac\xee\xf8\xcdb?\xe7\xb3H^b\xf7o\xe5|\xa9\ -\xb7\x90\xaey\xfan\xffhu|5~\x1b\xb76j\ -\xfb\x17\xa5\x01\x93\x8b\x8d\xb91\xd8%\xa3\xeeaB\x01\ -\xa6\xc5T\x17\x8e\x10\x98\xc1\xc3\xbb\x1a\xc8\xc1\xdc\x19+\ -J\xa0$\xa5\xc8^\x8b_\xce\xa0\x96%c\x13\x93*\ -\x81Ei\x90n\xa8\x82;g\xb1\xcd\x1a\x0d\x02#\xb3\ -l\xea\xf6A\x0b\xd9\xd0\xa2\xac\xd2(9\xb6\xd3@\xd2\ -\xc7\x16\x15\x04T/1\xa0\x09H\xee\xbaWn\xf5\x8c\ -\x98d\x11\xef0^7\xa9\xd6\xb9\x81\xda\x0d\xf8\xe7\xae\ -(\xb8\x97D\xdd\xcb\x84\x12\x81\xa9\x8b\x02\x88\xe1\xea9\ -I\x5c\xddc\xa5\xdb0\xaa\xa2\xa5B\x93[\xae<\x1c\ -\x93\x0f\xb6\x5c\x19\x0f/?\x9e\xae\xde\xac~\x5c\x9d\x9d\ -\x1f\x1dmn\xc2R{\xf6\xb7\xfe\xfc\xe6\xfd\xa9\xd4\xf7\ -g{\x0e\x9fM=\xc9u\xe2\xfd\x22-\xc9\xed\xa6\x8b\ -\x13\xf4\xc6\x22\x04g\xd1\xb1;2@\x19\x8d\xd6hf\ -0\xed\xa5\x83\xa4\xc4\xdavDd\x8ej\xe7\x9d4\xdd\ -v\xe6\x0f\x83t\x06\x8fh\x01J\xabhO\xa8\x03r\ -\xf1B\x934S\xcf3\x83+[\x96\xbd\xc2\x80\x96\x9e\ -c\x9b\x14\xc3\x91&[\xff\xc3\x14:\xc5\xbel;\x9a\ -\xb2|\x1d\xd5\x7f\x5cO\x91\xae+[\x7f\x17\xc9x\xfd\ -\x13]\x10\x0c6r\xbav\xdb P\xe6B<`\xc4\ -\x11\xb9\x17\x08\x92\x8e\xa5\x0c\x90J\xe1\xf4=/\x83\x14\ -WscL\x8d\x03\xd7\xce6\x98\x8f\x03\xc3\xea\x19w\ -G\xb5\xda\xe1_\x01Lr\xad\x15:\xa0\x13F\x19\x18\ -Ju\xf7\xbe\x15?t\x87w\xe4]O\x5c\x1f\xa5\x90\ -+\xf1\x98\xb9\x97\xe3V]\xea.c\xcf%\x88\x1a\x96\ -\xd7\x0f\xa9\xb7\x9fr\xfc@%*\x19\x1b\x8e\xffp5\ -\xedE{#\x1d\xc2\x8c}\xed\x90\xa4\xe8^\xe9`\x02\ -&w\xb4!\xba\xb9`\xa6\x0cQ\xab@\x94\xd4\xac\xa8\ -\x00j\x96\x12{\x8c\x80l\x9a9D2.\x1c\xca\x83\ -4\x11E\xf8\x10\xc9$\x0a\x0a\xad\xa3\x16y\xfa\xa0\x89\ -\x10\x84s\x0c1Y\x80\x9d\x8a\x0d\xa8\x80\xa6\xa1\xed\x15\ -\x011c]cn\xa2\xd4\xb0\x0d\xe9\x1f6\xd1\x00O\ -\xe3\x18\x87SC\x152\xa4\x0c\xb1\x918s@\x04\xcc\ -]\xb8LY>5lbb\xd8LF\xa8\xdcM\x12\ -\xe2H0C\xd5\x05\x13\xa8\xebh'\x93\x02\xa1i,\ -B \xd1\x8b\x95j'\x13CP\xf1\x8a\x1a(\xb9!\ -\x8dXQ\xb2E8$'I\x8c\xc2\x88\x82\xeb\xed\xf5\ -\x9c\xd0i\xa82bA\x5c\x00#\xdd\x87\xca\x92*9\ -\x0c\xee\x98\xf5\xc0\xc6\x834\xca2\x1e\xb5\xa2?\x8c\xc7\ -,P\xb8\xb0p\x15\x1d\x8f\x11BRl(M\x90\x1a\ -$\xc3\x10eSN\x994j\x02l#o\xe6V\x1a\ -!\x93\xd0=\xeb\x18,\x09Z\xca0-!\xe0\xc8\xae\ -\xa6\x13Cp\xe2Zbs\x87\xb4\xe9HX\xe2\xf5\xbd\ -\xfd\xda\x00#\x02K\xbc\xe8\x9b\xab\x9a\x07M\x0dfz\ -d0\x17\x88\x10\xa7\xd1\xf1\x1d\xc5(\x87!\x96\x90\xca\ -\x98k\x94KJ\x8e9\x8b)m\xc4\xb2 \x9b\x8f\x03\ -\xc2os\x93\x96,l\xb7\xa1\x80\x10c\x0c\x05\xaa\x12\ -|\x1b\x0a83\xc7P\xe0\xacY\xd6\xa1 #u\x1d\ -\x0a\x8ac\xacC\x81a\x8c\x91 -\xd7\x1d\xcf`\xa6\ -D9^\xd5`a\x8aZ\xa58\xa4\x92\x86,\xbc@\ -h\x18\xe6\x9e1\x84\x8f\xf7FA\xe0L\xe1\xebL\xe0\ -\x5c\x8a6\xacJG@\x88\x16\xcd\x0d4\x0d<]q\ -,IQ\x10\xcbFl\x08\x04Qv\x92.\x8e\xa8\x00\ -\x16w\x93\x07\xd1%D\xd3\xfa\xe8b\x10\x8a\xee\xb1W\ -\x08\x92\xb9\xc4H\xa7\xa99O\xf5\xc5\xc3\xf0\xa2\x8c[\ -gezl[\xc1m\x1c\xc2\x82L\xca}\x87\xb0\x12\ -iv\xcf!,9\xf2\x9eC\xb8\x17\xf5\x07\x0eA,\ -c\x1cg\x04\xc1\x22\xa5,\x9c\xc1H\x07\x00\xd3\xad\xd0\ -\xe0!\xa2\x12\x98c!2\xc7\x8a)\x10z(\xafE\ -U\xdc\xc8\x87\x92\xae\xea6J\x07*\x8a\x0c\x18){\ -\xe1M\xc5\x0c.\x16T\x9b\xb3\xd9H\x07-\xc8>\x9a\ -\xd8\xcc1\xc8$oF\x0f\x883Qd\xa3g\xc0\xac\ -h:U\xd9\xd6\x99\x03*^\x9c7;\xdd!\x84\x91\ -6\xa1\x04wM.\x0f|F\xc9\xd0\x1f\xf8\x0c\xa6\xe7\ -}\x9f!q\xa5\xa9\xfez\xe04\x82\xea\xdb:M\xe4\ -\x13\x9c\xa6\x10H\xb0\xd9\xdas\xc5\xb4\xf8\x9a\x95\x12%\ -\xd6\x8c\x92\x07\xa5\xef\x15\x06\x11*4bE4T6\ -1\x87\x0cE\x1a\xa2HC\x95\x81KP\xf2v\x03\xac\ -0\xa9\x8dd\x93\xa8\xae]6\x0aI\xe1\xc9v\xfe0\ -\x85\x8e{\xb12C\x86\x14\x1c/J\xb2\x84\xd9\xe8\xcc\ -\x02\x16\xa1J}(\x0aH\xd7(](\x12\x88\x94H\ -\xdb\x080\x81`\xe9B\xde\x87\xa2\x04CB\xe2\x0d4\ -\x1d0L}\x1d\x8a\x0a\xabQ\x1f\x8a\x14\xdd\xa2\xa3\xc4\ -\x0b(\x19\xf3\xd0\xa04\x90 \xb6\xb1\xa8\xb0\x15.C\ -\xc4\xc3\x08\xcc\x1c\xb0d\xe6b{\x9e`R<\xa8a\ -U\xda\x18\xd8\xccs\x13\x15\x87\xa4`-\x83\x1eT\xcd\ -\xe0i\x8a\xa6\xd0\x09\x9f4\xdd\xda'm\xd6'\xbf\xdb\ -\xbf\xfa\xf1\xdd\x9f~\xf7_\xea_?\x7f\x00\x9a\x00\x00\ -\ -\x00\x00\x15,\ -\x1f\ -\x8b\x08\x00\x00\x00\x00\x00\x00\x00\xed=ko\xe3F\x92\ -\xdf\xe7W\xf0\x9c/1N\xa4\xfa\xfd\xf0xf\x81\xdd\ -A\x82\x00\x17\x1c\xb0\x9b`\xf7[@K\x94\xad\x8b,\ -\x1a\x92<\x96\xe7\xd7_U\xf3\xd5$[\x14\xe5\x91'\ -A`;\x89\xa9\xea\xeaWu\xbdY\xad\x5c\xffm\x7f\ -\xbf\x8a>g\x9b\xed2_\x7f\xb8\xa0\x09\xb9\x88\xb2\xf5\ -,\x9f/\xd7\xb7\x1f.~\xfd\xe5\x87\xd8\x5cD\xdb]\ -\xba\x9e\xa7\xab|\x9d}\xb8X\xe7\x17\x7f\xfb\xf8\xee\xfa\ -\xbf\xe28\xfa\xc7&Kw\xd9_o]\xcf\xf5\xf6;\x0fy3_\xd4\xd8\ -\xb8\x9a'\xee\x90\xa8\xb5vJ\xd8\x94\xb1\x180\xe2\xed\ -\xf3z\x97\xee\xe3vWXc\xa8+#\x84L\xa1\xad\ -\xc1\x1c\x87u\xb5_\x01)\x0e.\xc6\xb5\xfa\xb3\x03\xf9\ -\x1f\xe0\xdf\xbaC\x05H\xb6\xf9\xe3f\x96-\xa0g\x96\ -\xac\xb3\xdd\xf4\xd3/\x9f\xea\xc6\x98$\xf3\xdd\xdc\x1b\xa6\ -\xa2~k\xde\xd6\x91\xac\xd3\xfbl\xfb\x90\xce\xb2\xed\xb4\ -\x82\xbb\xfeO\xcb\xf9\xee\x0e\xb8\x81\x19\xf7\xf1.[\xde\ -\xde\xed\x9a\xcf\xcb\xf9\x87\x0b\xd8\x1f\x17\xa4\xf8\x5c\xad\xe0\ -\xaa\xe6#\x92pV\xa0\x96\xc3\xfaM\xc2$4\xdaX\ -\xad\x88Ci1_k\xb8y>\xc3\x15~\xb8xH\ -o\xb3\xf8K\x9e\xdf'0\xed\x97\xf6\xc0\xf9\xe3\xee\xe1\ -q\xf7[\xb6\xdfe\xebb\x1c\xd8\x99\xb7M\xd7\xec\xfa\ -%\xad=\xd6\x03d\xfb\x87|\xb3\x8b\x17\xcbUV\xcc\ -6\xbd\xcb\xef\xb3\xe9\xc3r\x0d\xfb\xde\xe4\xf00\xdbN\ -\xf3\xfd\xf3m\xb6\x8e\x973`\xb5)\xf4[\xa57\xab\ -l\x9a\xcevK\x07\xb8OW\xab\xa90{a\xa6\xdb\ -u\xfa\x10\xc3\x80y\xf2\xb0\xbe\x0d\xce\xb4\x9f?\xc0\xd1\ -R.\x83\xad\xcfu\xebGh\xbe\x9eg\x8b-\xa2\x15\ -T\xc7O\x5cP\xe2\xda\xa0\x15\x18'K7?n\xd2\ -\xf9\x12\xc4\xa5\xc0\xf3\x86\x9c\xe5\xabU6\x83\x93KW\ -O\xe9\xf3\xf6\xa2F\x80\xa1\xda]\xb9\xa5\xaa\x1c\x14\x86\ -\xdd\xee\xf2\x87\x0a\x17\xcec\xf7\xbc\x02\xb2 0\x86\x11\ -\xf3\xcd\xd5w\xf4\x86I\xce\xdf;P\x0e\x0c\xb4\xdc=\ -_\xd1\xf7\x17M\x9f|\xb1\xd8f01\xf1`\x8em\ -\xa0\x07\xcc\x05Je\xfau\xb3\x91\xd0l48\x1b#\ -\xf5l\xd7\xd3\xf6\xb6\xbf\x8e\x8cNn\xaf\xee6\x19\xe8\ -\x99\xef\xfe\xf3\xf3\xff\xfc\xf4\xe97\xfb[\xac\x06\xc8\xcc\ -\x0c5\xa2n\xbf-\xa1\xbf\xae\x97;\xd0$\x8f\xdbl\ -\xf3/\x94\xc6\xff]\xff\xba\xcdzX\xbfl\xd2\xf5\x16\ -D\xff\xfe\xc3\xc5\x0e\x1fW\xa0|\xbfg6\xb1\x8a+\ -i'1\xb0L\xa2\x14\x93\x97\xcd\xfa(\x90DX\x90\ -E\xab\x1a\xd2<\x03\xd4\xc8\x84s!H\xb3\x96=\x03\ -\x5c)p\x04\xe3\xe12\x1f\xb7\xa6\xe2\x01z\xc1v\x03\ -T\x18\xb7K\x5c\xab\x15\x09\x17\xdc\x10\xdeZ+%\x0c\ -\xc0B\xb7\x96jT\x22\xb9\x94\xd6\xb6\x96\x0a\x03H$\ -\x869\xc4\xca!\xb6\x0c0\x9c\x9c\xe1\xef\x00{W\xcc\ -\xa5X,\x0f\xb3r\x85\xa5\x85f\xf1\x91)\xb3\xc5\x82\ -d\x8b1\x12\x95p\xca\xadPz\xdc\xc4$\xa6\xc3\x13\ -\xa7iz\x93\xdaQ\x13k\x06ZB){x\xe2\x90\ - \x86\xe8\x0bG'\xe9\x18\xfa\x0a\x8f\xbe\x7f\x06\xe1=\ -\x95\xad\x03\xc2{\x9f\xee6\xcb\xfd\xf74\xb1\xf8c\xe8\ -\x84\xc0\xaf*>Y\x09r\xccXB8cz\x12+\ -NA -\xb3\xdf@\xa6q\xa1\xe9\xeaU\xc8\xd8\x1e\ -\x1a\xc9h\xceFF\xa0\x95\xfb1\x8e\x8c\xe5'\x0aT\ -\x14\x0a\xf6\xcd)>R\x0d\xbb\xe5\xba!\xe2l\x8fD\ -D\x04\xe6i\x90\xd93\xba8\x04\x87\xa0\x0dt\x11\xc4\ -]\x04q7\x1f.\x90\xb0\xe0\xfb\x1cS\x95'\x91\xb5\ -=\x86\x16\x96\x0ds\xa9%_O\xde\x03&\xc6 \xe3\ -Q\xde\xe5G\x9dP\xaa\xba\xdc\xa8\x8d\xe6\xa6\xcb\x8d&\ -\xd1\xd2\x18\xde\xe2F\x0aS0nGX\x98\xd3\x1d\x1b\ -G\xad\xf1\xae\xc6\xc2\xfd\xbc\xd0\xb1\x81\xb9\xc4I\x8eM\ -h\xb6\xd1\x8e\x0d\xcc\xa6\x8e\xe9\xc6s\x08u\x87\x9e\x94\ -\x9aa\xe1\xf6\xb8s\x1c\xf7\xa14\x1a\x9d\xc8\x96 \x82\ -3c\xb4$\x9e6CA461\x82\x1a.[\x82\ -\x08\xeaPjmmK\x10)\xa0j\xc6H\x7f5}\ -%B@\xfbRI\xb8\x14\x13\x88\xa2('\xe0n\x10\ ->)\x1fQ\xb7 \x82\x90\x0a\xb4\x09O,W\x94q\ ->\xa1\x14,\x22\x01\xddz9\xc63\x0a\x90q<\xa3\ -\xcc\xf9b6\x8aQ\x82lI=\x0f\xf8\xb0\xafPE\ -w\x9d\x01\x13)A\x8b\x12O\x90\x83+\xbcI\x17r\ -\xd1q\x99H\xc2\x80\x8e \xee\xec\xfd\xa8\xf9Up~\ --\xa5b\xd4;\xc7\xe0\xfc\x86\xdf\xc8\xec\xa6;\xbf\xb0\ -\xe0MZ64\x7f\xd0=\xd1\x8b\x99\xea\x8e\xa5\x8d\x10\ -\x86i3Z>\xc1\x90\xff\x01\xf2\x09+\x1c\x90O\xcd\ -\xbc\x83tfP\x83\xee\x06\xcbF[\xd2'X\xa2\xa4\ -\x96\xac%|}\xd4E\x08\x15d\x8f\xb3DR\xa6U\ -?\xd4\x09\xf9A\x1a\xf8\x8b\x17\xe2f9\x08\x86\xd0N\ -\x0a-D\x02 r\x13\x87`\x15\xe3\xe8\x1aQ0\x14\ -\xda\xc0\xa3\x92\xc0\x98`\xd9/G*\x9bW\xb0,H\ -\xe9\xf1LU\xb8)/\xb5,\xcc\x0c\x88\xf0\xc8\xd9\xc6\ -[\x16f\x8er\xeeb\xb9\xdae\x9b\x9agp\xd6x\ -\xb9\x06\xd0C\x0e^\xc32_\xc7\x05\x06\x9c\xc4\xf6\x9f\ -?\xfe\xfd\xe2$B\x17]a\x19^Nb\x91\xfd\x98\ ->n\xb7\xcbt\xfd\xf7\xd5\xe3\xc6[\xf4\xb1\x01\x91>\ -\xf3O\xd9\xe7\xa5[\x16j\x14\xa54\xea_\xdd\xd9z\ -{\x06\x98\xddxD(\x96\xf4jb\x0bq\xaf\x8a\xb9\ -\x17$\x06DW\x04l\xd9\x8b\xa3\x0f\xe7.S\xf4_\ -\x85!\x0a\x9eA\xe4\xc0\x00\x1a\x10.\xd1q\x94-8\ -\x80V\xd3\xb6\xa3lUb\x94\x91T\xb6UD\x0fw\ -\x11\xc4m\xe9\x88\xf3\x8bfM\xcdo\xe4\xf8a\xd6\x22\ -\x96\xf1@`|n\xe7\x0f\x837\x98\xf1hf\xeb[\ -\x88\xa9\xa0\xaf!\xa6\x10\xca\x19\xcb\x055\xc3R*\xe8\ -A)=_b\x80\x92\xdfb3\xc0o\xe8\xf5\x9e\x9a\ -\xd6s\x81\x13X1%\x8c'\x18\x18:q\x9epb\ -\x98\xe7\xe4\xba\xd0\xc9\x9a\x84\x1aF<\x5c\xe6\x1cb%\ -\x8d\x1a\x9f\x9ako\xe5\x84\xdc\x5c{\x8d\xd4K\xd2U\ -\xf98\xa3\xa4\xa7\xa1\xaa|\x5c\x01\xfd\xba|\xdc\x0f\xee\ -'\x94\x1f\xb2\xb1\xfa\xfal\x94\x04\xff\x85\x07F\xd7\xf4\ -x\xf6\xe9\x8f1\x05\xec\xe4 \xeb<\xa6\xe0u\x9d\xc5\ -\xd7L\x9a\x10A\xbdLhX\x84OM\xeem\x1f6\ -Y:\xff9\xdb\xdd\xe5xD\xd9\x02W\xd5\x16p\xc3\ -\x13E\x88l\xe7F\x804\x84C\x10\xd9\x16p\x10\xed\ -\x84\xa3\xe7\xe5K\x10x\xc3\xe8\xae\xbc \xc0,6\xfc\ -\xad2\x1fD\xf0x \xf7\xe1\xe1\xd9X\x86\x06Mh\ -\x91\x00U\xc3\xb2z\xc0h~\xbb\xa4\x0b\x84$@\xd6\ -?\x81\xd5eZ\xbc\x8asL-\xa3\x82(\xd6\xd9|\ -\xd79\xd6\xea\xcf\xe5\x1cs{\xbe\x9c\xf2\x9f\xd29\xfe\ -&\x9e\x1c{%\x9e\x92BA\xacH\x91\ -\xca\x83\xa2\xab\x16\xc0eE\xceD\x8d\xae\x98y\xc5\x1c\ -\xca\xd1\xad{\x19\xcb\xf1\x89\xc5\xd7#\xd0\xa1\xc0\x0e_\ -\x0b\xbb\xd2+\x81Q\x9e\xc1\xfc\x8a!\xfa\xf8K\xfcW\ -%\x9d=\x91tC\xc5\x0c\x8c\x13\xad\x80\x11\xbb\xbb\x8d\ -\xa5L\xa8\xa1\xd0:aHI\xd8\xb6\xb9|\xa5\xc3\xf8\ -#\x89)\xf8k\x11\x93\x96\xd5f-Zb\xda\x0e(\ -a\xe8_\x92\x96\xfa\x9b\xd22.\x13\x9e\xe4/IL\ -I_]\xcay\x8b\x9aVb])\xb5\xafK\xcc\xeb\ -)\xdeKpO\xf5\xdd\x0d\xbcJ1\xff\xbc\xcc\x9e\xde\ -\xd5\xd4\xb8I\xeb\x9d\xe1\x85\x0eg\xd7\x81\x86E\x0cZ\ -6\xdc\xe4\x9by\xb6\xa9\x9a\x94\xfbi5\x95\xa6\xbf\xb9\ --\xe2\x1d\x19\x8eZ\xb7\x93p\xfb\xf6.\x9d\xe7O\x1f\ -.X\xb7\x11\xaf\x97`\xc0\xa6\xa42R\xf4\x9a]\x90\ -\xc8\x13\xad\x89\xe5\xa6\xd7\x88\xeb!\x22\xa1\xa4\xb1Cu\ -\xe3<\x9f=\xe2e\xa6\xf8\xb18\xea\x87}\xaf\xfb\xe3\ -f\x83\x08\xab\xf49\xdb\xb4\xaf\xd64\x97o\x8c\xa9;\ -\x96\xd7s<\xc8\xf6.\x7f*\xe8\x83\x95\x9b\x8fYw\ -\x06l\xf7w\xef\xe1`\xd3\xed\x06\xcf'\xd4\xf1i\xb9\ -\x86\x0equ!\x88\xca\x1eaJ\x8cj\x99\xba\xae/\ -\xeab\x00\x01\x03t-\x1b\x9f\xf1\x15\xa2\xbf\xa2\xc7\xe5\ -<\xdb\x86\xd7\xe4\xda\xe2\x9b\x9b|\x1fn\xcfo\xfe\x0f\ -\x047~Hww0\xc2\x22]m\x0f\xa1\xacs7\ -\x89\x8fR\xb4\xec\xf2U\x06\x227\xcb\x1a\xb7\x14i\xe4\ -\x83M\x8b-\xcb\xa3\x0b\xd2\x1e/\x04\xdd\xae\xf2\x9bt\ -5H\xe3\xfbt\xbf\xbc_~\xc9\xe6\x8d\xd7\xdf\x1e\xc3\ -\xdbq\xa9\xc2\x1a\x92\xc0\xe2*\xf9\xdc=\xe3\x95\xaf\xfd\ -3\xc2Z\xda\x08\x01L\x88\xc6I\xc3\xab_\xcb\xf5\xed\ -\x1e_\xb6\xa3\xa0\xd5\xecT7=\x07\x9a\xb2\xfb\x87\xb2\ -\xb5\x91\xa2(\xfa\xbc\xdc.o\xd0g\xf7v\x08\xb8k\ -\xbc'5\xef@q7%>\xae\x09\xb5\xe46_\xaf\ -\x9eK\xb4J\xa9\xf4u\x89\x83\xdfg\xbbt\x9e\xee\xd2\ -F\xb1T\x10.h\x951\xb9\xde\xcc\x17W\xff\xfc\xf4\ -C\x1d\x8e\xccfW\xff\xce7\xbf7!\x06\x22\xa47\ -\xf9#\xf0l\x1d\x9f\xe1}\xab\xd9\x15\xea\xd9t\xf7q\ -y\x0f\x02\x83\xf7\xf8\xfe{\x7f\xbf\x02\x15W7\xb4\x90\ -\x91\xda\xcd\xa0\xc5\xb0\x9b\xac\xb8\xa7\x17\xbc\xda8\x9f\xdd\ -/\xb1\xd3\xf4_\xbb\xe5j\xf5\x13N\xe2\x85M\xe5\xa0\ -\xcb\xdd*\xfb\xe8\xe6,\x1e\xab]L\xcbmTA\x8f\ -\xb7\xcb\xebiE\x06\xf7\xe9\xb6!\xcf\xadVu\x22{\ -\x17\xaa\xf06\xa0\xd6\xa4\x92LMb\xbcz\x90\xc5\xea\ -\xb2\x22\xe3m\xcdU\x01\x0b\xc4\x85`\x8a\xa1\x01\xa7\x09\ -3`\xb3\xb1X\x8fY\xc9a\xc2\x89k\x96V\x13\xbc\ -\xd2@t\x02\xce&\x07\xbfS\xf2D\x12a\xd9e\x9b\ -5\xb1\xd4\xb7\x09\x937 \x82\xbd`\xb0\xa9\xc4\xb4\x9a\ -\x12\x0a\x91\xe4\xfb\x05P\xf0\x0ah\xfb}\xc7\x10\xbbk\ -V\x97\xae\xd9\x0b\xeb\xb7\xbbM\xfe{v\xb5\xce\xd7\x0d\ -+\x96)D\x98O\x0b\xe5\x07\xdb\xa5\xc6\xeb$\x8f\x1b\ -u\x8c\xf5\x0e\xae,\xd1ks\xaf\xbd-P\x80\xb7J\ -Y\x81\xafc)\x12\x10=\xdd\x1a*DRM`o\ -\x10\x8fL\xbc\xc78\x0c\x06\xa3\x7f\xe9\x07\xdcm\xa2\xe1\ -\xa4B\x83\xec\xd2Vi\x83[\xa2J\x14k\xec\xaa\xbf\ -+\xeen\xa5\xb0V*\xa5\xd2\xfdX7\xa7x\xef%\ -NA;\x1d(\x0f\x1e:\x1d5\xf2t\xceL#\xb4\ -\x09\xde:+\x05S\xa8\xcct3\x1b\xdaD\xaf\xd0\xdd\ -\x98\x13X\x0cg\xd6\xc2\xb4\xf29\xd5\xf4\xeeE\xa2D\ -\xd2\xb7\xd8\xa9iw\x97M\x9c\x226\xa1\xf6\xcd>\xc0\ -\xa8M\xebs\xa0\x15\xb5fD\xa5\xc6\xd0EL\xaa\xc1\ -\xa3Y\x84wh\xac\x90BF\xa0\x0b\xb8\x01\xc50)\ -:Gq\xf1\xb7\xfeX\xe0\x81\xa0\xd7-U\x8f\x06P\ -\xe2\xc21\xd4\xff\xae\xa2\xb2\x91D_B\xcb\xdd\xee\xd2\ -M7\xf1U\xb5ekX7O\xa8\xa0\xd22}>\ -6\xa1\xd4\x82\xefF,x\xcc \x1c\x9aI+\xed\x80\ -l\xbdH!Yr\xb2B\xb2\x01\x85\xe4\x0eJ\x9a\x83\ -\xb2\xeb\xbf\x96\x18\xd4H\x01\xe5pNa\xbb\x9e\xde\x96\ -\x0f\xbe\xcc\xf5g\xa0\x89\xd4\x960\xa5'\x18\xd1X\xc6\ -\x85b8A\xfd\xdc \xc4\xb0\x13\x89\xaf-\xc1\xe0p\ -\x99H\xce\x98\x97\xbe(8\x1a\x0c\x0d\xd7\x10\x13\xd5\xd7\ -G\x1cKS<\x22\x09\xa1e\x14\x9b\xc4\x80M2`\ -\xa9\xaa\xfb!Q\x5c>\xb6@U\x0fd\xef\x1a>\xa9\ -\xba\x9b\x16\xb0\xe9G&M\xcf\xa8\xc2\xf5\x11\x82\xbd\xfc\ -\xc9\xaa\xb5\xd4\x13\xf5\x17\xd7\x88MK\xc6\xfb\x17^Z\ -\x0ab\xa09|\xdf\xa6\xa5\x9eZ\xf7sj}\xe6_\ -\xfa:\xae/\xc1\xe8\x8f\x91\x80\x80V\xae\x99j\xc0\x1d\ -\x81xPIK\x84\xbbG\xa0(\xb8\x18\xcaqj\xfd\ -\xdc \xc4\x94\xe3=\x02\xc5\xc05Q*1\x10*\x8b\ -\x9eCB\x14V*\x8d\xb3\x1cM\x5c\xe1r\xd9\xbe0\ -W-H\x86G\x885b|\x1d&\xc0\x15b\x22\x84\ -\x96o\x96\xb7\xcb5F\x0a?G\x10\xc4\xe3}h\xd0\ -\xa7\xael\x0e\x96\x1b\xfd#\xa2\x10\x90+\x108\xd3\x00\ -\xdduP\x87'Ub\x0d\x07y\xf1`Z4}k\ -\xa0\xc5\xb2\x02c\x19\xf5\xc6\x03!\xae\x06\xac'n`\ -\xd0\xdb\xda\x04\x5c<\xc1<(\xa3<\xe1\x94\xf9\x03\xd6\ - o\xe2\x1a\xe6-\xb0\x1e\xad\xd9G\x7f\xbf_\xa2\x16\ -\x19d\xf9\x80\x8b\x01\x86\x84\xe7\x06\xc6\xd0Aq\x9d\xa4\ -I\x84T\x1c$\xa5\x81\xb5\x16S\x01-I\x0c*\x14\ -\xd3\x0c\x07Q|k\xfb\xc2\x91\x5c\x82\xc9\xe1\xa6h\x94\ -\xcc\xef\x07\x9f|\x02\xc3\xc7f\xf6\xba_\xbd\xc6\xc0V\ -\xbeD\xe3}\x0e\xbc\xbc3$B\xef\x8b\x9a\x99\xa2k\ -s\x87\xe22\xe4\x8a`\x01W\xd7\x1d\xa8V7\x11`\ -cA\x1b\x80\xee\x8c\xf1\xee\xac\xb1\x85\x99\x07\xa9\x11\x0a\ -\xe0\x13*\x12\xa2-'\xca\x83U\x0f\xa8n\xcb>@\ -\xad\x02\x916\xad}|o\x06\x84\xe1\xfc\xb1\xebg\x14\ -\xa8\xbf\x1a\xe4\x8d\xef\x16e\x09Q\xa8\xa8\x01\xd3\x15i\ -5\x08q\xbf\xcf\x97\xe8\x1e\xd7\x95\xe01\xcd\xe0`\x12\ -\xbc\xc9\x05\x9a\x22bx\xa1\x10q\x91C\x15%\xc8\xd3\ -\x15\xa8z\xc0\x9eE\x0fh\x8d\x1d\x22\xe1E3\xce\xd6\ -\xc2G\x80C\xe6\x8eb\x05h\xe2:Y\xaa\x08m@\ -%6\xee\xc6\xe1s\x0bB\x81h\xee\xe6w=\xb7\x8f\ -^0\xcc\x11\x83\x0d\xd3P\x8dQW\x91\x16\xac?R\ -\x89%\x96\x92\xd1\x89J\x984\xc4\xdd*?\xe8\x1a\x87\ -F6TS\xa6\xa9-G\xae?r\xe0\x16\xd8\x1a\x9c\ -\x01\xac\x1f\xc2Hk\xf5\xe5\x00oA\x88iy\xcd\x5c\ -\x16\xdc\x1c\xed\x98\x8b%(E\xc8[\x12v\xccE\x03\ -*\xff\x16\x9c\x85\x1d\x94\x8a*\xb4\xba\xb1\x8b\xec\x8d\x8d\ - T?\x8e\xaf\xa4\x01K\xc4=X3z\xb9\x1e0\ -\x07\x8e\xaf\xf0\xde\xa1i\xda\xe3~\x97\x82\xad8\x90\xa2\ -\xe0+\xb0\xa1\xd6P\xc7Wx\xcf\x19\xfcjP\x14\x10\ -\xec\x98\xf2\xb3\xfb/n\xa3DD\xfeE\x14p8]\ -\x9b\x1b'np\x8b\xcf\x0e[\x0b\xea\x18\xaa\x84\xba\xca\ -0c\xb8\x01\x9d\xe5\x01\xebN\xb8\x99\xb2\x1b\x8f\x0aT\ -\xca\x8av\x19\xb5\xf1\xdb,\xd5\xc4*$\xf0\xee\xb9q\ -y\xa5 \xa0\xc9\x15}\x7fPY\x092h\xefC\xca\ -J\xd0\xb6\xb2z\xb3\x8aoV1\xe0C\xe1\x9dd0\ -*\x96\x04\xa3\xe1\xb0KvP\xd3\x1dSO\x9a\xf5\xb4\ -\x93\xe4\x07u\x93fQ\x892\xac\x98\xdc\xa8\x1d\xbd$\ -\xad\x12\x03zII\xaeJ\xbd$\x88\xcfP\ -\xc7\x81\xe6\x9am\xb7\xdbf\x09\xcd{Z\xdd\xbc\xbcu\ -|$q\xdd\xd2\xc2\xb9P\x86R\x05\x8bp\x8c\xc4A\ -\xf1I|w\x817\x93\xf1\x8b\x94\xc1\xe9\xd0x1Y\ -BDH\xf1\xd5\xa5\xa4R\x15@+1N\xa5\xa8\xaf\ -\x8cf\x10Xb\xc8\xc3\xb8\x9d\xa8DP-\x84F\xff\ -M\x80<\xc0,\x16B1x\x8c\x98\x04v\xc0\xea\x0d\ -\x80 sB\x9c\x86\xdf.\x0aS\xe1\xfb\x7f\x98\x07\x5c\ -Bf\xe0\x1c\x89A*\xd3\xc40\xae\x15G\x98b\xc4\ -h\x80a%\x19\x84\x7f\x1cdY\xc33\x9c8,\x97\ -\x09\xbcH\x8dN\x9d\x02\xd7S\xe2\xeb}\xd0O\x04\x1c\ -8\x15\xdc\xab\xf7\x0a}\xd8\xdd\xc1;\xe0\xa3\x22-\xaf\ -n|\xbd\xcef\xbb|\x13\xcf\x1e7\x9f\xd3\xdd\xe3&\ -\xc3j\x96\xfa,\x8f\x96C\x94\x89\x00\xe9\x8a\x1c\xac\xb5\ -\x04\xc3eW\xa0\xa35a\x9d\x82\x07\x8dE\xdb\x94\x03\ -J}\xff\xd4+\xe2\xf1\xaax\x8e\x95\xf1\xc8@\x1dO\ -\xa8\x90\xa7\xc6\x8f\xea\x81\xab\xc6\x0e\xae7p\xd9R\x0f\ -\xdb^\xc2\x81z\x86N\xcd\xd2P\xb5S\xab\x92\xa1\x7f\ -\x11\xb7U\xc9\xd0\xbf\xbd\xdb(\x5cJ\xba|\xe1\x05\xe5\ -L\x00[\xd2\x81\x17\x1e\x9c\x8e+\x08\x1b*p\x08\x0a\ -x\xbf<\xed%\xabc\xe3R\x06\x0d5\x18\x19K\xc1\ -\xb1\xf4\x1f:\xba\xc0\x91\xff\x059\xbe\xab0ry5\xba\x19_\xbe~}\xda{\ -\xbf\xd9\x5c\x8e\x86\xc3\xe5U=\x9e\xae\xde6\xf5du\ -1\xc4\xa3!$\x0e/.\x86\xbd\xbb\xde\x8f\xf7\xddu\ -\xbc\x9c\xce\xda;\xe2\x01:\xda;\x89\xa7=y8\xc2\ -\xf5E\xb3\x19\xef\x1fL'{\x01\x97\xd7\xebE\xbdZ\ -\xbf\x1bN'\xc3f\xd1\x5c4\xcb\xcd\x15\x84\xe8}\xdf\ -\xc9]\xdf\xc9\xba\x19o\xe6\x1f\x1b\xccu\xb1\xc2\xcc\x1c\ -\xb6\xbcz\xbe\xeb\xb9>X\xd7\xa7O\x9f\xeaOVz\ -\xe8\x9c\xf3P\x99\xa11\x03\xf4\x18\x5c\xdd.7\xe3\x9b\ -\xc1\xc18\x00\xd66\xce(\xa5\x86x\xb6\xed\xf6\x84.\ -\xa3\x9b\xc5|\xf9\xa1s\x0d\xf2\xb4W}\x9aO7\xef\ -O{.]nz\xd5\xfbf\xfe\xee\xfdf\xd7\xfa8\ -o>\xfd\xb8\x02l\xaaR\x95K\xf8\xe3\x09\xdf.p\ -\xb8+(\xc2l\xb1\xfa4\xfa8\xbf\x9a\xbf]4\x7f\ -j\x96c|\x0d\xde\x8e'\x1f\xde\xadW\xd7\xcb\xe9h\ -\xd9|\xaa\x0e\x06\xce\xa7\xa7=\xac\xcd\xf3\xfa@\x8bt\ -\xef\xc5\x09\x17\xb2\xdbU\xaf\xda\xdc^b\x82Ms\xb3\ -\x19N\xae\xaez\xd5\xbaY`$\xa7\xbdz\xdf4\x9b\ -\x22i:^\x7f\x18\x5c\xac\xa6\xcd`r}\xb5Y]\ -\x0cd3\xc3/\x94\xf5\xaeY6\xeb\xf1b/Lz\ -\xdeI[\x8c\xa9\xea\xcd\xf2\xa1\xdc\xd6%\xc9\xe8/\x10\ -\xb3\x14\xed\xda\x8byVU'\xd3fvUz\xe1\x22\ -\x04\xd7\xe3]\xdc_\x8f\xa7\xf3\xf1\xe2\x9f\xf9\x05\x8d\xad\ -\xdem/~]\xce7\x98\xf2\xfa\xaaY\xffr9\x9e\ -4\xff\xb6\xfc\xf5\xaa\xe9\xed\x1f\xbfY\x8f\x97W\xb3\xd5\ -\xfa\xe2\xb4w1\xde\xac\xe77\xdf\xaa\xdaF\x97\xfb\x8a\ -\xff\xe1\xd2\xf7\xb5\xabs\xf6\xb9\xaf\xfdw\xbdjv\x0b\ -\xcd\x88\xb5\xca\x1ag8\x83^\xb8\x5cg\xe5,p=\ -\xedYU\xabdC\xaf\x9a\x1c\xf6\x9a\x1c\xf6\xe2\xc2\xff\ -\xe3\xf5\xab\x9f\xce\xff\xa6\xfd\xdf\xb6K\xc7\xe2\x01\xd6e\ -Q\x0f\x5cx\xef\xf7J\xc6\xf6`\xb2Z\xac\xd6\xa3\xe7\ -\xc9\x9f\xfd\xf0\xf2e\xafZ\xcdfW\x0d4T\x15D\ -\xda\x04\x84V\x01J\xd9\xf3\x1f\xf2\x9d\x00\xbd\x13p2\ -O\xc2\xb5\xfd`\xbc|\xfe!p5\x88\ -5N\xdb\x82\xab\x89\xb57z\x87\xab7\xe8\x99\xb6\xd6\ -;p\xb5\x11\xa3y:\xae\xb9\x13\xd7\xd4\xee\x8b^\xca\ -\xe7)\xb8\xa6\xd8* \xe7\xb3\xb3\xcf\xb1\xde?.>\ -\xd8\x88\x1bA\x97\xf8\xa0Cm\xbd\x0e\x12\x1f\x10\x05\x9c\ -f\xf0\x9f\x1c\xf6\x9a\x1c\xf6\xba\x83\xd4\xa8NH\xb3\xfb\ -\xc2\xf8\x90\xdb\x03\xcc\xe7\xc6\x87\xdf\x13R\xed\xa8\xc3\x88\ -\x19\xa6/_Z\xd0\xd5\x80\xd7\xd6\x088\xa1\xaf\xc5\xf2\ -\xed\x16c\x9dj\xc5\xb8@\x88m\xaauH\x82p\x86\ -\x19\x04\xfa\xe3\xc9A\x9f\xc9A\x9f\x03|u\x17\xbeA\ -\xb5\x07\x9c\xfb\x1a\xd7\x89oPO\x0b8\xaa\xf6\xdd\x22\ -\xda\xcf\xd8\xda\x10:\xb4\xfe)!\xfcQo\x92U\xe4\ -\x19h\x1b\x5c\x7f\xffU\xee:\x040\xeb\xfa\xbe\xf6!\ -\xc4\xad;\xd1\x1a\x01\x9f\xc6Io\x92\xeb\x10L(\xce\ -\x84\x06a\xb2)\xbed\xf7\xe0\xc9\xae\xc4\x98\xces\xd1\ -_H\x04\x82\xee8\x97\x1f\xf3\xf9\xf1\xb9(\xefB\xb7\ -\x94\xf6\xa3\x09\xeeG},\xc5:\x1b\xbb\xa5t\x90<\ -}\xa6\x8e\xa5\x04\x15\xbb\xd5D\xb7{\xd7l\xcf\xeei\ -Z\xb2\x8f\x08\xf9}<\xecW\xd45\xeb\x8a)\xdf\x14\ -\xce\x85\xe3\xd9\xb2Nx\x87h}*\xba\x86\x86\x93\xd3\ -y\xba\xb2\xd9Ne3\xed\xba\xf2\xe4\xb8\x15L\x87\x9a\ -\x84\x1f\xed\x17\xb2N\xe7jg\xdd\x16\x0e\x04\x15\x13\xf7\ -p0\xcf\x08\xb1\xc0\x01\x0e\x1a]\xf8,\xdbs\x9dp\ -\xd8v\xdb{:\x1c\xb6\x1d\xcf/T\xb2\xdb\x92\x86\x84\ -`\xd3\x16\x0e\x0bJ\xb8c\xe1\xf0D)\xee\xd8\x22c\ -l\xd4\x9f\x93\x93\x98\xce\x14-\xd8/d\xe1\xc1u\xb1\ -\xf0\x10\xce\xce\xbe,'\x09\xc8\xbd\xa8\x107%_\xf3\ -\x8a\xd4V\x92\x92\xfd\x13\xc2aj\xa7\xec\xe7\x90<\xd3\ -\x99\x94\x04\xd7\xeeA\x9eL\x9e\x83kg\xdf_H\x9e\ -oKV\x16\x5c\x8e\xfb\x14M;xB\x81\x03\xd8\x04\ -\x13\xb7\x9c\xd7\xe9\xda;\x97?\x07\x8e\xce\x5c\x22t$\ -\xf0g\xfe\xa5z\x9avt$\xf0\xfa<\xba\xf3v\x82\ -\xf6\x10\x8e\x93!+$r\xc5R\xdft\xbc\x19\xcb\x04\ -\xbb\x86\xc7n\xb7\xa3o\xb0\xc1\x0f\x0dF\xbdm\xde\xcd\ -\x97\xa7\xdf\xfc\xf7\x7f\xfe\xd77\xec\xfb\xcd_\xfck\xf5\ -\xfa\xf2\xacy?\xff\x97\xdf\xd6\xcd/\xbf\xfd\xeb\x9b\xc9\ -o\x1f&y\xfa\xcd\xc9p?\xa6H\x18\xdd\x5c\x5cR\ -r%W\x9b\x0fD\xe9\xe7j\xb3Z->\xcc7\x95\ -\xad\xd5\xc0\xe4~5[\x8f/\x9aO\xab\xf5\x87J\xd7\ -\xe1\x0e\xbd\xdd\x92v7\xc8-\xa7\xb3\xd1\xbf\x9f\xbf\xbc\ -\xbb\xb3\xbdw\xde\x5cM\xd6\xf3\xcb\xcd|\xb5\xac\xd8\x1e\ -\xbf]]\x03\x89\xeb\xeb\xf9t\xa4\xa7\xde\x197\x9e\x0d\ -\xdeNfa\xe0&6\x0f\x92J\xb3\x81\x091\x1b\x03\ -\xcd\x9fNL\xefP\x22d^B\xc6\xcf\xeb\xd5\xf4z\ -\xd2\xac_<\xfb\x81\xc5\xd1\xea\xe7\xf3\x97\xd5b\xfev\ -=^\xdfV\xbeV\xeadx\xdc\xeb`\xf8\xf0\xde\x9a\ -~\xff\xf5\x0e\xff_\x88\xbc\x87\xea\xcd\xf8rt\xc6\xda\ -ps\x8e\xff_<3H-\x06\xf8\xd3\xea\x8d2#\ -\xfc\xe9\xf0=/\x00\xed\xfd\xae\x0f\xe4\xbc^M\xe7\xb3\ -\xdb\xbd\x1cG9&\xbfQi\xe4\xf3\xc8\xc5\xbf\x16\x11\ -\x87\xbd\xda\x97\xb2Z\xbf\x816\xee\xce\xf8\xa7\xc5\xe2\xfa\ -j\xb3\xe6\xedJ\x83\xf0\x1d,d\xd7\xf1\xe1J\xb6j\ -z\xb8\x16=\xd0\xe9\x8dN#\xe7F\xda|\xaf\xf4~\ -O\xc7\x9d\xffP\x95y\xb8\xf4\xd7\xafG\xe7\xab\xc95\ -k\xf9?\x9d\xbfx&\xa2&j2V\xd3\x94\x06S\ -;U\x037m\xdc \xa5\xd9\xdb\x81\x8fa\x12\xa6!\ -+\xedg\xb2\x95{\x83\xff/\xb72\x9d\x8c\xc8$\xc7\ -p;\xf3\x8b\xf1\xbb\x86\xa5\xfe\xefo.\x16\xf0uw\ -O\xee\x8f\xd8\xcc7\x8b{Z\xb1]\xde\x0f\x8b\xcd\xfd\ -\xfb\xdb'\x8b9\x8b\xd5\xa3R\xa7\xbe\x19\xc0\x93\x8e\xaf\ -\x17\x1b,g\xb6ZL\x9b\xf5@\xd7\xe3y\xd9\xfeb\ -\xfe@\xf4\xb0U\xb6\xac\xf1\xc1Z\x1e\xc7p2\x19\xfd\ -\x85\xde\xf2\x00\xbbnH>\x03\x91\xdb\xcbFd\xae\x9b\ -\xab\xd5\xf5z\xd2\xb4\xbe\xfc\x99N.\xe6\xec9\xfce\ -3_,~\xa2\xf4c\xd7q\x00\xef\xb1K\x19n\xd7\ -}\xe0\xcf\x87\xf7\x1c\xfa\xc9\xf0\xd8\xe9C\xd3v!\xe4\ -~Pj\x96\x88D\x9f\x8eC\xce\xf1\xf0\x93\xcb\xf1\xe6\ -\xbd\x047^x\x16\x9eq\xfd\xbar\x11\x09\x81\xef\x93\ -\x08'[\x9d\xb1m\xd9\xf2\x99\x97\xca%6\x5c6\x95\ -\x033\xca\xd2\xd1\x9bP\xbd\xaa@\x0d\xac\xc5S_\xdb\ -\x14+\x9b\xea\x94\xa4\xd0\x03)\x93\x8au\x0a+\xe5\x0a\ -\xe3\x22[\x1as\xe0\xcb\x85\xcc\x96\x0d\x96\xad\xa0\xa5\xa5\ -s\x92D\xc7\xb3\xe1\x9cd=F\x07\xb6B\x96\x8e:\ -\x99J\xf5U50`d.\xf4-\xdf\x1b\xa0\xe5 \ -7\xa3eM\x92)\x95T\xf9Tp\xd22\xa5\xe6\xa7\ -\xf5\xc3\x96\xf1\xa6\x0f\x05M\xd6V\xfc\x8aX\xaa\xad5\ -\xa5\xb0\x0e\x8d\xa5\x1al'W\x8bj\x80\x1c+\xb1\xd0\ -\x12\xb1r\xd9W\xd2,(jm8\x12I\x16\x1a0\ -R,\x06`\xe5\xbe\xc6\x0c\xa6\xec\x83\xe9\x9aOe\x1b\ -\xc9\xe2\x895z\xd7@g\xc8cyL\x03h\xac\xae\ -\x14st\xe2\xb5e}\xc7\xc5\x88\xe9\xb1(\xaf\xfb\xd8\ -u\x8a\x06\xd3\xb3c.\xc9 \xafC`i3x\x5c\ -k'\xc5\xa0\x0c\xd2\xa0\x95\xc3d\x11\xd8\x02,c\xfa\ -,\x0e\xe9\x8ae\xce\xd8\xc7\xe1\xe1|0Hy\x8aI\ -\xba\xe2LY\xc48\x5cK\xd9\xae6F\xe6\xe6\x8e<\ -\x93M\xa7\xadLn\x00}9\xd9J\x8eR\x8eF\xc6\ -\x05-Gh\x83\x915\xcaYc\xa9l\x18[\xc6H\ -\xc3\x16\x01\xb2g\x9dd\x8cJl\xec\x94\x85\xd7N\x19\ -\xd1\x15\xcfU@\xa1(\x1b\x99\x09\x92\xb6\x02A\xf4\xa5\ -o\xd8\xe2\x81\x86\x17duY\x1f\x08|\xf5\xdb\xdd;\ -HX\x04lt\x84\x11\xdb\xd7c\xe0\xab\xfbW\x98Z\ -\xa9\x7f\xda\xbf\xdd,\x8d\xcd]2.\x97\x0bD\xa7o\ -\xcb\xe2d\x8f\x91EL\x1a\x12\xe4x\xcf\x14\x93/\x02\ -G\xef\xd7\xcd\xec\xb4\xf7|o]\xb7$\xaf\xd5\xcd\x9e\ -\xc2\xfe\xbd\xd3\x22I\xee\x8b\xda\x99\xef\xb6\xe2v\xa2[\ -\xa7\xdd/\xccn\xa7=\xb6\xfb\xa0vv\xefa\xce\xb1\ -o,\xad\xb82\x1a\x87\x0c\xe3\x0e\xb4{Qs\x9b\xad\ -T\x19E\x99C\xa4\xa6\x89U!\x0d\xa15\xe4\x80\xb3\ -Q5,WC\xb3T\xd1\x0f\xda\x18\x8fO\x0e4Q\ -\x8b#\xf5\x01\xb1\xaaOS\xe6\x10\x9d\xa0\x81b{*\ -%\x19d\xb7eM\xf6\xf4\xa9\xa8\x81\xa5^\x86r\xce\ -\x1e6z\xac\x06i\xab\x06\xb6\xa8\x81;P\x03\x95\x8a\ -\x1e\xc4\xa2\x07\xa6\xb8\x1e\xd1\x0a\x9d\xad\xd8e\xd1T\x93\ -r\xd9\x984|8P\x98\x19<\xf9\x08>\xfe\xdb\xe7\ -\xfb\xb7\x90\xdf\xb5c\x19\x05\xcb\x0b\xa2\xe7\x89\x1e\x5c\xa7\ -\x98\xc6\x1d*\xc7\xdb\xe5&\xf2^\x97\x1f\x991t\xcc\ -\xc8<}7\xa3\xd6\x813fG\x9f\x91\xbdT\x81\x91\ -\x9eqJ\xc3ma\xca\x88\x19C\xe6\xb5g\xfa\xdfj\ -\x14\xfeO\x0ff\x8f]\xb3\xfb\xdd\xec\x16gJ\x8f\x96\ -\xe1\x85'\xf4\x83F\xd9b\xff\x96\x8e\x10\xbe\x8b\xdeH\ -\x9cb\xa23\xf2\x86g8\x88\xf0/\x121B\xdc\x06\ -\x09\x06\x02\x19ME0}\x0e\xa5\x0bC\xff>\x87\x1a\ -\x8c\x82\xf3\x83\x17\xed\x07b\x87Ae\xae2\x15\xbb\x17\ -\xbfc*v\xd7\xdc5\xe7z\xc5\xd8\x95\xe1F4r\ -(\xf8\x06\x845\xa2\x02\x8c\xe8\x98\x13\x9f\xf2Xr\xad\ -\xb1\x10\x046\xc41\xc4/]\xfd\xf5\xf7\xf7\x1a\xb6h\ -1\x1c\xeb\x81\xd7\x88\xa1\xcd|\x89\xef\x03\xafq|\x04\ -\xc9\xde\x1d\x81\x92\x97Y)\x84\x7f\xdc#\xd09\x13r\ -\x12\x06a\x16>h\x80n\x8d\x80\x9e\x10w\x12\x19\x06\ -}\x11=LDh\xf4\x07g\xf0@1S\x87b\xa6\ -\x9dY\xc0\x04\xb4\xc3\xba\x93\x98\xf4\xa4Dz\xba\xae\xe4\ -\xe9^\x8c\xf1\x8c\x8c\x8e\xfbc\x14A\x80Sa\xb7\xbf\ -\xc8\xfd\x95 \x9b<\xa31Eh\x81k C+v\ -\xf7\xc5\xb0\x84\x00\xc1Y\xfa\xc8\xfdY+\xcc\x89\xf88\ -\xec\xcf%aK|\xbbH\xf3w\xdc\x9fC\xd0O\xc2\ -S^q\xba\x94\x03\xcf\x04\xee\xea\x8cM\xc6Y\xa0\x0a\ -M\x95\xf7\x8f\x89\xab\x84L\x83\xa5d\x84\x7f\x09\xda\xf7\ -py\x9e\xf3d2\x9b\xb5\xe2\x91w\xe4\x0e\xa2\xe1\x04\ -!@c\xcf2\x93Q\xf0\xf9\x9c%$\xde7I\xa6\ -\xf4r\xf0\x11\x94\xe3\x151\x8c`r\xc0\xd0\xf9\x02\xa1\ -&WpYh\x06:b\xffF\xfc\xb3\xc1\xe6aA\ -G\x0aR\x00Dg\xd2\x0b\x1c-;\x97*\xb6!5\ -)\x9c!\x08\x0c@%\xc3\x87i>\xcc\x05?07\ -\xe0\x17\x9c\x95\xf0\x04\xc8`\xa1Z\xf4\xc3$\x9ah\x06\ -izD?r\x87~\xf0E\x8b\xe8\x87\xf5\x0c\x03|\ -O\x96\x0c\xc9\x84e\xc9\x5c4\x16\xb3\x04x\x08iD\ -(K\xc6-\xc7\x86\x0d\xdc 8\x22N>\x80\x22\xc6\ -\xaf\x7f\xd2\x85u\xa2\x1f\xc4\x88\x83\xc5\xca\x10\xb5X\x8c\ -\xd4<\x1e\x1fM\xa1\xb2\x95\xce\xd4l\xe1\xae.\xb6\xba\ -wW\xdc\xfb\xf3\x99|\xda\xd1\xc9;tx~0X\ -\xb1c\x9e\xe3=\xc6m\xb6\x8c;\x09\xe3\xe6\x91\xa1k\ -\xc0\xfc\x08h\x1a\x86\xc4\x16\xf5\x03\xe7\x0a\x16\xf1\xa0\x05\ -'\x8b\xf4@\xc3\x17\xe0\xc8\xa3O2\x91\x05\xd2JK\ -\xbcT\xd4>[;\x1b\xd9J\xa2\x13\x90\x01?\xc4`\ -\xeay\x06\xc4\xd2A\x10\xae-\x14\x02\xb2\xd5VG\xef\ -\xd1m't{w\xfd8\xdb\xb6ml[\x1f\xb1m\ -\xdf\xca\xb6\x93\x90mq)\xb0\x02\x92mK\xb2\x8d)\ -\xee\xc8\xb69 \xdbF\xc8v\xea&\xdb\xae\x90mw\ -L\xb6\xedC\xb2]\x88h\xa0\x15\x16\xae\x8d\x14\xaa\x92\ -\xa4I\x88\xb3P\x02\x9dDKBv\x845\x96\xf4\xcc\ -\x8aqm\xf33{\x94\x9f\xb9\xa3\xfcL\x17;-\xe9\ -Y>L\xcf\xf2Qz\xe6w\xe9Y7\x9b1\xaa\xdd\ -,\x83R;\xc5CL(\xc9c\x14\x8e\xdf\x92=\x1e\ -\xaf\xce\x1c\xae\xae-y\xf4\xf7\x92G\x98\x94\x11,\x18\ -\xd3<\xb3<\xce\x07\x97\xb7\xa3\xbb_\xc5\xc7aP\x9d\ -\x09s\x86\x86\xf8V\xfb\xd4\xad\xc8\xe8\x1dK\x87If\ -/\xf6\x00\x05\x05\x99I\xf2\x92\xdf\xd0\x86\xa0u\x88\xc2\ -\xe4\x92\x89\xd1U3\xec\xea\x82\xa1\xa5\x85\xc9\xef.\xb0\ -O\xac\x03^\xd4I#\xe7\xd4\x92\xd3\xc2\x1fIJK\ -t\xa1\x86x\xa4%\x95df\x9c\xe9wH\xa4\x8bu\ -\xdc\x19\x87\xde\xba\xb2\x80\xb5\xc0\xb6aQx\x0e\xd5\xc5\ -J}\x9f6\x9aJ\x8d\x00\x9a\xc4J\x81~,\xb0\x1b\ -\xdd\xa1!f\x17\xc8\xa8\xb7\x9a\xf6n\x8d\x13\xb7\x10\xc5\ -\xb38\xfaQ\xfe\xa0\xb0/o\xae\x98\xf1\x1a#\xfe\xdd\ -f#\x09\x8a5\x0c\xf3\xd6\x97\xd4\x11\x81\x86G\x8bt\ -\x16\xff\x92\x1e\xc2\xf9\x821\xd7\xd9\xd0a\x04\xc5s.\ -\xe0\x0cv\xe8\xf0\xc7|\xe5t\x09:\xdcr\x12\x06\xe0\ -9Lr\x09#\x19\xc9B\x14\x93\xa9\xdan6\xc9C\ -\xa4\xaf\x96\x22\x83\x22?+\x13\x0e\xca\x8c\x03N\x99\x1e\ -\xb3\x1c\xd3\x85K\xda\xd1@@\x00?\x98%\xbd0\xa2\ -+L\xe5J\x86\xa4%\xef\x22\x8da:G\xf7\xa8x\ -\xe9%\xb3\x8b\xc2\x88\xb0\x0fF\x8f\xac\xa9\xb9\x81{c\ -F\xa1\x99\xa9\x90\xd616{\xe6- \xfa\xec\x17\x94\ -(\x1e\x9c\xbd\xc4\x1cdw\xc0=\x1b)\x12\xc0\xbf\xa0\ -c\x8c\xc2\x908S\x02\xdc\xd8\xb2\xc7\x13\x83FrV\ -\x042\xc2\xd1\xc0\x8b\xddFU\x0c\x5cf\xabM>n\ -\xb4YLz\x90\xb0\x18\xdb\x05S\xde\xaa\x0f\x0c`\x1b\ -:\x02\x9d3+<\xd0\x1emS\x091\xa4\x04^\x5c\ -;\x0e\x93\xe6\x16$[\x04mB\xf2D{S\xe4{\ -8X6r\xa1R5\xa9\x0b6o\xa9\xfe\xcc\xa4i\ -\x898M\xe8\xbf\x96\xc8\x94@\xadZ\xb5I\x17m\x8a\ -$\x97\x89>\xc4\xe7c\x1d|%\x18\x97\xd0\x19\xc5\xb6\ -\x99R\x8am\xe7b\xdbZK,5\xce\x1co\xed>\ -G\xec\x8e\xfa\xc1\xaa\xbd\x0aYn%q\x01\xe2\xaf2\ -Uhg1$\x1e\xf87Gz<\xe7\xb71\x96a\ -\x95\xac\x1a\x9e\x80!\xdb\x98\x5c\xd4\x8e\x8b%\xa7\x82\xd3\ -\xa0\xcd\x97@h\xa8\x9f\x06X{:\x08Q\xd6H\x92\ -c\xb9IM\xd51)\x14\x85\xd5\xb1\x14\x8e8\x83)\ -\xf1I\xea\x02\xb6D\x1dV\x94\xc4\xd0@\xc0%\xdd\xf7\ -\xf6\xbe\xf5<\xba\xe5\xb8\xf3&q\xc7?\x22]\x01Z\ -\xe2L\xa2\xff\xa3\xc8/\x09%\xd3S0J\xab\xda\xf3\ -r\xfbP\xcd]\x87\x9a\xbb]\xfa\xe3\x88L\x9f?\xb8\ -U\xe2\xbd9E\x99a\xeb\x8e\xa4<\x93K\xe8\x8e\xe4\ -0\x11\xfa5(K\xdb\xd1r-\xf5>J\x09f\xcb\ -\xad\xb6Q\xde\x89u\x22K\x90m\xc4\x12c\x8d\x94\x1a\ -t\xa1OY<\x1e\x94(J\xadH~z\xa9AZ\ -\x19o\x09\x18l-}\xdd\xb4\x8c\xc8\xb2\xa0\x09\xad\xc2\ -\xee\xdb\x91\x8d\xf23^\xf3\x10\xe0\x8eBO\xf0\xe6\x0e\ -`\xfe\x84\x94\x9a\xab\xc4\xa9s\xaa2\xd3vWq\xbb\ -+b\xe2\x85\x85\x96\x1c\x5c\x09O,+\x1cl76\ -\xd8\xee\x8c\xd5\xbdR\xd2\x84\xd1\x0ddH\xbf\x8c\x97\xb8\ -\x11\xa5\x1c\xad\xc4\x07\xd1\xe3\x84,X\xc2\xb3\xc4\xe0\x91\ -\xfd\xe3\x9ea\x5c\x93$\xba\x92\xdf\xfbjj\xb0\xda\x9d\ -\xdfW\xca\xf0\x095\xbd\xe5\x80?'y\xb2\x12\x1f\x14\ -\x97V\x8b\xdbw\xabe\x81\xb9\x5c\x07o{\xd5\xe5j\ -\xbe\xe4\xcf\x0a\x1cy\x89!{\x80\xbb\x00EL\xf0\x0e\ -V8\x1e\xdf6DV\xb6\x09\x8b\xa6#\xa1\x1a::\ -o[\xdd\xf3\x07\x08\xbb\xd3q~dJVDwS\ -\x92\x88\x82xB\xaeC\x98\xb6\xf0l6\xf4\x1d\xcf\xc0\ -\xc9\xfb\x0e\xe4\x1c\xaeT\xac\xbbcvG\x91\x22\x04}\ -\xc7v5\x18\xbcEz`K\xf0\xd3z[\xe1\xad\x06\ -\xfcU2\xcf\xa1\x10af`\xd8q\xa9\x93\x07z\xc5\ -\xe8\x8bGQN\x18X\x96\xc3@\xdcU\xb4QO\x13\ -9nt\xbb\xc7\x13\xbe\x06{\xf1?\xc6\xc8&\x02\xb0\ -5\x00\x00\ -\x00\x001:\ -\x1f\ -\x8b\x08\x00\x00\x00\x00\x00\x00\x00\xec}ms\x1bG\x92\ -\xe6\xe7\x9d_\xc1\xd3|\x19E\x00\xc5|\x7f\xd1\x8cw\ -c\xd6\xde\x99\xd8\x08O\xec\xc6\xcd\xee\xde\xde'\x07\x04\ -\x822ohRGR~\x99_\x7f\xdd\x00\xc9j\x08\ -j4\x80\x06d\xc9g\x94)\x03\xd5\xd9Y\x95Oe\ -eeeWU\xff\xe1\x9f~\xfc\xee\xfa\xec\xfb\xc5\xdd\ -\xfd\xd5\xed\xcd\x17/\xb0\xc0\x8b\xb3\xc5\xcd\xfc\xf6\xe2\xea\ -\xe6\xcd\x17/\xfe\xf3?\xfe4\x8d\x17g\xf7\x0f\xb3\x9b\ -\x8b\xd9\xf5\xed\xcd\xe2\x8b\x177\xb7/\xfe\xe9\x1f\x7f\xf3\ -\x87\xff1\x9d\x9e\xfdyq\xb3\xb8\x9b=\xdc\xde\xbd:\ -\xfb\xe3\xc5\xed\xeb\xc5\xd9\xbf^_\xbf\xbb\x7fXf\x9d\ -!\x15(09\xfb\xeb\x7f\xfd\xf9\xec_~|{{\ -\xf7p\xf6\xef\xd7\xef\xdeL\xff\xf5\xe6\xac,3\xffk\ -U\xe6\xab3+\x00g\xff\xfc\xee\xea\xfa\xe2LQ$\ -^\x9e\x9dM\xa7M\x11\xf7\xdf\xbf\xf9\xcd\xd9\xd9YS\ -\xbf\x9b\xfbW7\xf7_\xbc\xf8\xf6\xe1\xe1\xed\xab\xf3\xf3\ -\x9b\xfb2k\x8b+\xf3\xdb\xef\xce\xff:\xfb~\xf1\xa7\ -\xdb\xbb\xff\xb5x}\x8e\x05\xce_\xd4;f\x1f\xbea\ -Y\xd3\xa6\xfc\xff\xbaZ\xfc\xb0\xb8\xfb\x97\x1f\x1f\x167\ -m5\xee\xcfy\xfd\xf6\xab-\xb7w\xe4\ -\xbf[\xcc\x1e\xae\xbe_4\xc5|\xd7T\xa8\xbd\xb3)\ -\xfb\xb7\x1d\xe2\xbb\x8b\xcbg\xea\x1f~\xf8\xa1\xfc\xc0K\ -\x22\xcc\xccs\xa0s\xa2iC1\xbd\xff\xe9\xe6a\xf6\ -\xe3t\xfd\xd6\x06\xcc\x0f\xddJ\x00p\xde\x5c\xab\x94\xbb\ -Q\xbd\xfa\xf1\xfa\xea\xe6o\xbd\x95Y^\xed\x96\xde\xe8\ -\xd3\xdb\xe6\xef\xf9\x86\xa7\x8cr\x7f\xfb\xeen\xbe\xb8l\ -\xee\x5c\x94\x9b\xc5\xc3\xf9W\xff\xf1\xd5\xf3\xc5)\x94\x8b\ -\x87\x8b\x0e\x9b\x86\xe9\xfd|\xf6v\xb1V\xeeS\xe6\x0a\ -\xaf\xd9w\x8b\xfb\xb7\xb3\xf9\xe2\xfe\xfc)\xbf\xbd\xbf\xab\ -\xe0\xb8\xcc\xb8\xba\xf8\xe2\xc5\xd7\xb3\x9f\x16w\xdf\xac~\ -\xffpu\xf1\xf0ms\x99b\xf9\xf3\xdb\xc5\xd5\x9bo\ -\x1f\xea\xef\xef\x1bm\xf9\xe7\xdb\x1f\xbfx\x01gp\x86\ -\x14gO\x17n\x1b\xce\x97\xd7\xb7?|\xf1\xe2\xfb\xab\ -\xfb\xab\xd7\xd7\xab\xe2\x167\xb3\xe6\xeb\xf4\xf5l\xfe\xb7\ -7w\xb7\xefn\x9a\xc2n\x16?\x9cun~\x92\xea\ -U[\xdbF\xa0\xb7w\x8b\xfb\xc5\xdd\xf7\xab\xdb\x9f\x00\ -x\xf5\x5cm(L\xcbKORu/\x89\xad\xdfu\ -q;oph\x98\xbe\xb9\x9d6|\xbf\xbf\xba}w\ -?};k\x10n\xda\xf0\xef\x1b\xb4\xafg\xf7\x0d\xed\ -\xf9\x7f\xde7\x1c\xcf/f\xdf_]\x9c\x7f\xb5\xb8\xff\ -\xdb\xc3\xed\xdb\xf3\xfb\xa6\xdf\xbf\xbe\xfdq\xbd\xe4\xdbw\ -\x0fo\xdf=|\xb3h\xbb\xce\xaa\x0a\x0d\xf2\xb5\x19V\ -\x97\x97E=g\xae3X,\xcd\xc1\xf4\xf2\xeaz\xb1\ -\xaa\xe7\xf9\xb7\xb7\xdf-\xce\xdf^\xdd4\xb0\xdf\xdd6\ -_\xe6\xf7\xe7\xb7?\xfe\xf4fqs\xde\xdcq\xddb\ -y>\x9b?,\xbb\xe9\x86Loo\xde|\x90\xfd\x8f\ -\x17o\x1b}3/\xfa\xc1\xcb?\xd5\xcb\xff\xf8\x87\x8b\ -\xc5\xe5\xfd\x93V\xb4\xdf\x11\xda\xdcF\x89\x17\xb3\xbb?\ -\xdf\xcd.\xae\x9a\xae\xbb\xc6d~{}\xbd\x987\x0a\ -2\xbb\xfea\xf6\xd3\xfd\xb3J\xad\xdf\xc2\x8e\xde\xf0\xb9\ -o\xa0l\x08Z\xd8\x1f~\xban\xe4m3\xa6\x0d\x8b\ -\xc6r\xfe\xf6r\xf9\xf9\xfd2\xeb\xb6\xd1\x85\xab\x87\x9f\ -^\xe1\xef_\xac\xe8o//\xef\x17M)\xd0\xfe~\ -,\xa2\xa5d\xc7|qv~\x00kx\x9f5n\xb0\ -&\x5c\xb2>_\x97\xe5\x1f\xff\xf0,\xfc\xdbFQ\xde\ -.\xe6\xad\xadZ\xd3\xa5\x87\x9f\xda\xae\xb9N\xc6\x17\xef\ -\xa9\xed\xdbo\x9a^4U8k\x0c?\xb4\xff\xe2\x06\ -\xc1O\x8d\xc0\xed\x05X^\x87\x8d\xeb\x7f\xff\xe2\x85\xc3\ -&\x83\xf5b\xa7\xb7wWo\xae\x1a\xe5\xe4%\x91T\ -\xd2\xa5\xa8\x1d\x19\x08\xa9\x11\xf8\x97+\x1f\x9a.\xe5\xfb\ -nv\xf7\xb7\xc5\xdd\x1a\xaf\xa6\xc1\xe7\x7fk\xc9\xffx\ -ww\xfb\x03\xfeeq\xb3\x12\xa7\xe1\xbd\xb8i\xb5\xfb\ -\xdd\xc3\xed2\xe3nq\xf9\xbf[S\x03O\xbf\xfe\xbb\ -\xfez\xff\xfe\xaa\x8cO\xc6\xf1\xd5\xa3m\xfc}\xd3\x19\ -\xde\xce\x1e\xbe\xad\x1a\xd7\xfebV\x7fT\xc2&\xe7/\ -g\xad\xdb\xd0\xfc\x9d}}\xa6\xcd\xb7\xa9.\xbfN\x91\ -\x8av\xb2\x97\xb9\xcf\xa4\x7f?{\xb1\xd6\x0b\x1a\xd3r\ -=\xbd{w\xdd\xf4\xf6\xef\x177\xb7\x17\x17M'\xb8\ -\xbb\xfd\xdb\xe2\xd5oa\xf9y\xfc9]Z\xfdWX\ -\xe0\xed\xc3\xefW\xf0L\x1b_\xe7\xee\xe1\xd5M\xe3\xeb\ -<\xf5\x94f\xa0\xbf\xb9oF\xa8\xef\xbex\xd1Z\xa3\ -\xc5\xef\xa0\xc8\xcb\xb3\xbb\xdb\x87\xd9\xc3\xe2w\x18\xf0r\ -Eq\xbd\xfc\x09\x13x\xb9\xea>+~;\xc1\xbe,\ -s\x04\xf0\x95C/\xf4}\xc8\xcb\xa7\x88\xfc\x16\xe0G\ -\x83\xfd\xd7\xd1`\xff\xf5P\xb0\x0d>/\xb0\xa9\x0b\xb6\ -\x1d\x80\xf5\xd7#\xed\xc9\xd7\x87\xda\x13\xfc\xcc\xecI\xf4\ -\xda\x13*z\x08\xf0\xa3\x95\xfc\xebC\x95\x5c\xe2\xf3R\ -\xf2\x18\x80\xfb\xa8~\xc1rP\x97C\x9d\x02\xa4\xa8\xf7\ -o\xf5\x09\x964B\xc5\xda\x8f\xf7:\x06\xe1\xad\xa4\xe3\ -\xfd\x5cC\xe4\x01?\xb76\xd6>~\xae!\xb6\xbe\xcb\ -\xfe\xac\x87\xfd\x5cC\xf4\x0f\xfb\xb9\xed\x97\xd9\xf5\xcep\ -,\xe7\xba\xaf\xbe\xbd[4s\xf3\xdf\xae\xf3b\x02\x7f\ -\x86l\x9d-Q\xe8\xf2\xd2\x9b\xc7\x8c\xff\xbc\xb9zh\ -&\xdf\xef\x9aY\xd8_\xdb)\xe1\xbf\xdd4\x13\xb25\ -\x8a\xff\xa8\xca\xfb\xdd\xec\xe1\xee\xea\xc7\xdfa\x01!R\ -\x99@\x9b\x0a\x92\x01\xca\x84\xb0h\xb6_\xa6A\xc5\xd0\ -\x13_.\xf9\xcc\x1b\x05\x94,\x80\x98\x8e\xab\x9cF\xe9\ -\xa8\x98\x0b\x83\xc72\xe7r\x83\xe6r\x83\xe6\xae\x9dE\ -;\x89ar\x8f\xfel\xaa\x08\x11\xd3\x90\x8a\xbc\x96\x14\ -\x9a\xed\xa9\x22D,\xc3*\xb2\x90\x8b\xf9<\xdfc=\ -\xa0!Dl\x1f\xd6\x90\xfa{\x84\x86TX\xfa\x10\x0b\ -\x1e\xad!P<\x125\x97\x1a\xf2\xfc\x83\xb0\x80\x85\xca\ -\x84\x8b\xa4\x13\xc5bJ/WU\xc5e\xfb\xa32\xac\ -\x0a\xff\xa9\xc9p-\x8c\xc0\x18+\x12\xfa\xe2EP\x91\ -\x10\x13Z\xd1P\x87fg\x8d`b}\xb1\x15\xbb\xda\ -\xa6\xb5Q\x98\xd874b\xdf\xc9\xf5\xd9y\x0f\xeb\xdc\ -P\x89='\xd7\xa7\xd3\x97\x0aZ\xbf\xbe\xe0\x11,\x0a\ -\x22\x09\xe7\x04\x0b\xb0\x0a\xdbd\xda~C3\x9e\xb4\xd7\ -L\xc1'\xa6E\x1d\x94'S\xc9\x82B(\x1d\xe5\x89\ -\x22\x19j\xf4\xac=P\x02QA\xd2\x9e\xd5G\xa28\ -\x93\x13?\xab\x0f\x156u\xb2\xdcC}\xe0D\xb1\x15\ -\x82\x1db+\xa4m\xda7\xb6B\x88?\xeb\x98\xe3y\ -\x04\x0dQL\xafcNH\x0aMPJ@B\xa3\x12\ -\xee\x05Q\xfdS\x18s\x98HO\xa4\x22\xe4\xc3*2\ -[\xb4i\x805n\xb2\xce\xd3\xab\x08i\xbf\x8a\xf8)\ -\x8c\x08\x14\x8e0\x8f\x09\x14!0\x91\x89AA\x08\x9a\ -L\x85\x0b1\xa8\xad+\x0cd\x84<+L\x14Ie\ -$\xaa\x0a\xe3\x85\x1d\x22\xe2Ya\xac\xb8\x03Z\xfa\x93\ -\xc2pIW\x10\xd0\xdd\x15\x86I?<\xea0\x9dl\ -\xd4a:\xe1\xa83,1\xf0!]\x04\x8a\x05\x8b2\ -\xd9`_a\xd0S\x85\xaa\x19N\xef\xc23p\x7f_\ -9\x86\x83\x96\x99\xd9:\xeb^B\xd0\xc4\x16S\x9eh\ -13\xf0\x5cLeB\x05$\x18\x9a|j\x07c\x12\ -\x22\x96\x96\xc8\x8b\x06\xb2z\xed8,\xc5\xdc\x8d\xf3\xb9\ -\xe3x\x11#r[\xe5\x5cn\xd0\x5cn\xd0\xdc-\xfb\ -\x96\xa6b\xb6\xe0\x1e\xd5y\x01\x8d^\xe7\xc5\xf1\x08X\ -\x86\x9a\x02\xb5\x8en\xa6\xae\xec\x8e\xa7\x19\xb5Y\xa1i\ -,\x13D-\x8a\xc4\x93)aI\x16\xcf\xea\xbc \x16\ -&\xe3\x90g\xe7E\xbdppf>\xfb.(E\x92\ -\x95qEC\x95f\x0f\xdf\x054>lg\xc0`w\ -;\x933\xb7\xcd)\xf5\x0e\x93!\x9e]\x0cO\xc5\x0b\ -$%\x01\xdb\x8b\x8d:Z\x9f-\x03\x87\x0d6\x0a\xc2\ -\xe8[m\xda\xfc\x82\xe6\xbb\x9aK\xb0\xd8(\x22Q\x1d\ -9\xf4\xc5\x80\xc8\xbb\x97A\xbb\x9b\xe4\xd7\x19=\xacO\ -j\x97@\xa3\xd7.\xd9x7O\x0b\x99\xb3`ku\ -\xb4\x80\xa2\xa1(\x9c\xa6{\xc4\xd6\xd4O\x14[\xd3\xcdj6h.\xbb4u\x9e#`\ -\x11\xfbD\xde\x12\x07\xf4\xe5\xf5u\xb3ti\xa3I\x07\ -&\xc5\xcb\xa9\xf9\xde|apF\x9c\xba\xeb\x04\xa7u\ -\xc7\xe0\xd9U\xa3\xe8\xfaq\xfa\xec\xe6Q\xec\xd8\xf6\x9b\ -\xd0a\x985\xe3\xcao\xfe\xe1\x1f\xfe\xe1Q\xceZQ\ -\x0ck\xdd\x9f\xde\x09\xce|\xfe\xc18C\x05\xb4\x91\xb2\ -\x9f3\xf5p\xfe\xe1\xdb\xab\x87\xc5\xe6\xc4\xa9\x0b\xe8#\ -\xdfO\x03@M\xf3>\x005-\x07\xc4<\x08>M\ -\xc7\x13\xc3W\xfb\xda\x7f\xff\xe5\xeb\x7f\xfd\xea\x1b\xfaf\ -7\x88*\x9e\x15\xe7.\xfe\xb5Q\xaal\xdd\xda\xf5*\ -\xdc\x9f\xfe\xf4/\x7f\x04x\xb1\x86\x03m@T\x85\xec\ -e\xf3\xe5\x97\xef\xb3\x91\x8a\xc8\x11\x03\xdd+\xdc\xf2\x9b\ -\xbe\x19\x22'\xee\x01iJa\xe1\x8a+\x02\x15\x16\xf1\ -\xfa\x08\xc3\x8a\xb2\xda3\xc0)E\xf9qf3^\x0c\ -\x84-r\xf0>rhG\x04^\xab}\x98\xae\xd5>\ -L\x8fU{\xdcR{=B\xed\xbdPr\xad\xbc\xb6\ -?\x8fUw\xdaRw\xdf\xa7\xee\xd6\xa9\xbb\xd4\xbaG\ -\x01\x00\xaa\x95\xb7\xd5\xef\xb3\xf3\xea\xbb\x1c^{\xff\xa6\ -\xcfc\x91]<\x96\xea\x8b\x02U\xa7\x02\xa90\xc8\xb3\ -\x83\x89\x9cE5\xf4HU\x8e-U\xf6#W9W\ -\x96\xe7\x0f\x8d\x1f\xd6,\xd0\xbe\xf9\xb7\xd7\xff\xa7\xa9\xe0\ -\x92f\xf1\x7f\xdf]\xdd-.\xeaz\xfe\xfd\x97\xef?\ -\x19\xd3\xa6|X[\x13\xbe\xbe\x22\xfcY\xda\xb5J\xac\ -\xc6\xb4?\x5c\xbdz\xfb\xe6\xf2\x7f..7\xb0ZV\ -\xe1\x9b\xabZ\xf27\x0d\xe1\xea\x9e\xf3\xa7\x9bZ\xc9\xce\ -\xd7\xb869\xbf9\xa5QEb\x87\xcf\xdb\xaaVA\ -\xe8s4\xab\xb5\xfa\xf2\xf9\xd9\xd5Zy;\xada\x1d\ -_\xfd\xad\xb5\x8f#\xfbjG\x8e\x9b\x13C\xf6\xd5^\ -0\xf6\xe9\xc0S\x97\x12\x04\x0e\xdei\x03(A\xecu\ -u\xc7\x14\xa3 a\xd6\x8eP\x89>\xaeh\xf9y\x8a\ -Vg:\xfd\xb3 \xe7}\x94\xce\x8bc\xba\xd4\x9e\xe3\ -%\x02k\xf7\xd9 ir\x18\x8a\x08bh_\x1c\xa3\ -\xb3\xee\xb1\x88\x83\xa0-\xa6\xed\xf2\xc7\x8f\xd8\xc6\x9a\xee\ -\x1f\xb5\x8d\xab\xcfs\xfc6^\xe7\x8b\x9aa\xcf\xde\x0d\ -YI\x95\xae\x8f\xc3X@\x14TW\x91\x9d\x0d\x9a\xcb\ -\x0d\x9a\xc6\x11\x8a\x92\x16\xa8<\xbc\xecK%=&S\ -*\xa9\xc9\xb1\x8c\xe3\xc3\x04J\x82\xba\xd3\xc4\x8b)\x90\ -\xeb\x04\xa1\x84\x07\xf8\xcb\xe1V8\xb9%\x0eS?\x81\ -%n}\xac\xf5\x92z\xcb\x97\xfc\x04g\xeda\x8a#\ -'\xee\x95\x13\xf7\xcc\xdd\xdb\xbc\xc6\xfd<\xa2\xf9\x0b\xb3\ -\xe8oi\x97}\xba=gQF\xaa^O\xed\xee\xf5\ -b} \x8b\x05\xc3\xf5\xf4\xfa\xea\x22'\xd0\xd7\x8f\xd6\ -\x08.v\xeaF8\xf9$\xd34\xe4\x18\x8b0<\x9c\ -\xf8iQ[\x066\x89&Y\xd8\xd0\x81p\x82\x05\x8d\ -\x9b$/\xf7\x9b\xaf\x9e:,`\x1a~\xb8\xf8\xd5\x01\ -\x88f\xd8\x1f\x19<\x18\xaf\xa8=2\xa6\x12\xd4\x07\xca\ -P\x00\x019\xeb8*\xc5\x15P\xea\x03\xe5u\x9a\xcb\ -\x0d\x9aF\x0c\x82\xe2\x81\xa4\x03\x8a\x81K\x85\xc0B\x89\ -\x86\xd0\xaa\xc7\x14\xb5\xa8'\xc7\x0e\x03\xe6\xc9cB\xf4\ -y6\xfep\xe4\x88\x22?\xedN}\xdaP7\xfdB\ -B\xdd\xf4Y\x87\xba\xe93\x0eu\xd3\x09C\xdd\xa7\xef\ -\xfdN'\xeb\xfdS\x8cb\x1c\x946\x99Z\x09\xb66\ -}b\x83:\x91\xeb1\xec\xfa\x14\xb3d\xfb\x89\xc94\ -\x0e\xb0\xf0\xcd\xce\xcb\x87\xc5\xddN\xfb\x01W\xa4L\x82\ -/\x9a\xfb\x16\x7f\x9e\xbd\xbb\xbf\xbf\x9a\xdd\xfc\xf3\xf5\xbb\ -\xf6\xfe!\x0e\xed\x04\xe6\xe2\xab\xe6P\x8f\xd9\xc3\xd3)\ -D\x9c\xa2(\x9d\xa5\x00\xebL\x99\x84W\x8b\x02V%\ -\x9f\xb4=\x04\xc4\x8e\xd1\x1eT\x04\xda\x0f.\xa6\xb6\xef\ -\x90{\xe4\x89\x81!r\xfff\xce\xacs\x16.\x12n\ -T7\x83\x89\x17\xc8\x08\xaf\xc3\x0b\x16SO\xcb:\xc0\ -P\x16$V\xb2\x83\x82\x0a\x838\x1a\x97\x94\xb4hA\ -|.s\x8aE@\xdc$\xaa\xfd*\xc4a\x18\xf9\x5c\ -\xf9)r\x09\x93Lz\x92\xb0\x12\x1d\xbc\xa4\x81)c\ -x\xcbE\xc4\xf6\xcdn\x9b8\xd4\xe0\x95\x97T\xc9:\ -\xa88\x171\xb2\xe8\x0c9^\xda\xdf\x18U.(,\ -\xe68\xbcf\x9a\x99\xf9\xd9NF\xfbI\x9c`\x16\x17\ -\x0f\xe3\x09c\x81\x0ce\x7fy0BD\xe0\x83\x081\ -\xf1\xe1\x08e\x09I\xb0:\xa7\x86\xce,\xb5\xa0)@\ -G\xa9a\x08\x94\x09\x17~\x84\x85\x8aj\xf2r\xa7\x0a\ -\x14\xe3\x0c\xf2\xc9\x14-\x0b1:N,\x8b)X\xe6\ -(td\x18\x1d\x94C\xd1!-\x1e\x86V\xf5\x87\xb3\ -\x90\x05V\xfd\xa94\x9d\xb5\xf0\x5cP\x9dx\x87\xe81\ -\x96\x5c~&p8\x0a\xac\xca\xc3\x91\xe4\xccOEG\ -\xaa\x86@\xd5\x8c\xaa-K\xc5\xe0@\xb4Gm\xc1D\ -\x1e\x85\x0e\x8e\xd0\x91O\xcb\xc6$\x1a\x89\xc6D\xa0X\ -\xfb\xd1\x09S\x01\x0b\x03\x1a\x83\x90\xe4)m\x0cS\x85\ -F\x0a30Q\x1dW\xb4\x04\x03!Y\xed?@\x85\ -\x11I\x86W\x1d;\x0a\x85-\xc1\xa1\xe5\x06JQe\ -\xf5\xc9\x94d\x14\x1e\xbe\x83\xc6\xc0\xc1V\xc5W\x9d\xbe\ -\xc2\x82$\x05\x12\xb5\xc2\x92\xb9\xa4\x09\xad\xdb\x83\xa98\ -\x85\xb1\x1c4,M\xa6\xba\xeau\xab^\xb5\xca\xb5Q\ -\x18\xe9\xa0\xcd\x11\xc6C1R\xc9\x02\x86,\x1d\xcb\x9b\ -\xc5]\xac3\xe1B\x93b\x96Us*\xcdv\x8c\xb2\ -8\x9aj\xe4bJ\x8f0!\x03br;\xb5a0\ -\xb4\xf4F\x9f\xb0(\xb0\xf28\x9c\x06m\xb3\x0b\xd1\xbe\ -8\x1d{d9\xd2\x88\xd7\x8b\x02\xca\x98\x11\xea\x97o\ -\x83IYF\xd8\xe0_\xf8\x18N\xca\xe3\xc7\xf0}\xf6\ -2s\xdf^f\xdd}\x03\xd4\xdc\xe7\x17\x17\xbe\xeb\xc6\ -9\xd1\x18\xb7\x97\x19\xc7\x9f\xa0\xd1\x07\x07\xcai\xce\x02\ -`\xb4\x93\xedo\xc68\x1d\x1cp*8\xe8tp\xc8\ -\xe1;\xdd]\x88>\xd8\x1f\x5cHv\xef\x0f\xaf\xa5I\ -\xb6\xff\x86\xc0\x9c\xe7|\x86{o\x08T\x96\x18f>\ -\xbfh:)\xeey\xd2\x84\x0b\xd9\xe1p\x920~\x10\ -N\x12\xe6\xe3\x1c\x950^7\x8b\x90\xa6\x83\xe7\x8b\x8d\ -:\xea\x0ee`\x9b\x86\x9b,\x92t\xbe\xf8\xf0Y\xa4\ -\xc3'\x9c\x0f\x1f\x87z\xf4E\xc5\x99}\xea\xef \xa3\ -\xd5\x7f\xfa\xb8\xf4g\x82$\x935u\x87\x02\x14\x9a5\ -\x90\x00\x051\xa0\x1b\xe4\x89\x02\xc6\x0a\xfe\xa4\xed\x95\xe6\ -\xc0\xe7\xaa\xeet\x8c\xe7\xaaP\x14\xc0\x98d\xcb\x83U\ -w\xe9>X\xed\x9b\x8a\x9b\x15\x22\x11\xad\x8f\x01\x95\x0a\ -a\x88\xd5\x85(\xc6\x059\x14\xb5\xa2\x97V\xc4Bl\ -\x87\x87\x0a\x1c\xc5\xc3%&SX\xb1\xf1\x1c\x11\xc1\x22\ -\xb1\xe1gt\x06\x87G&\xac\x08\xb9V\x93\x87h\xc5\ -1\xc4\xab]\x84\xc2\x86\x12\xa4V\xa37\xbe\xc2#~\ -\x06@\xe4\x94\x80\x84.\xf1\xd0|\x06Dr\xa9\x1fN\ -\xd5\xc5\xb0G9\xea\x10\xeb%\xad\xa5\xd9\x0d\x0e\x9d\xc0\ -(\x04h\x18\x01\xa0C\x110-\x22\x81HU%\x9c\ -K\x068V\x04\x88\x8bk\x98\xd4\x1e\xe2Y\xd0\x89d\ -\x8c\x5c0,W\xea\xcf$\xd7p\xcb\xa6\x8fkV\x8e\ -\xd1\xe2\xf7\xac\xf0\xb9k\xac\x9c\xd5s\xd3\xac\x1e\xeb\xf2\ -\xa8\xb1\xf3\x9a=\xaf\xd9;,0E\x0eN\xca\xe5\x02\ -S(\x8a\x1e\xea\xfb`\xb0^W&\xb6\xbeU.\xf2\ -\xcdx\xc9\x91\xb0\x88\x10r\x05 \xa2D\xa0\xf3'\x06\ -\xc3\xa0\x8d33;\xf8\xf1\x8e\x17\xf2$\xae=\x81\xc8\ -\x8au|\x03\xe5\xe2!\xa8\x5cc\xd2\xc4\x85\xc9\x91V\ ->\xd3\xdb\xbb\xc5\xec\xe2/\x8b\x87oo\xdb\xda/.\ -\xdb\x92w\xc2\x0aJ\x18\x82\x18\xb7HEAC\x8e\xe6\ -k\x116\x0c\xf3Q\xfd\x87N:RZq\x05\xef\x0c\ -\x0c\x1e%9\x99+l(\x85\x5c#\xaa\xf9p)\xae\ -\xfd\x90\x8d\x90\x96\xf2\x84\xd6\xc2\xa0\x9eif\x85E\x14\ -\xeav/\xa7\x02\x04\x81\xb5\xcf\xac\xd3\xcc\xbb4C\xee\ -*\x14\x0d4\x0a\x9d`qs7oc\x00T,\xc3\ -HV\x01\x81\x12\xe1\x91\xd1d\xb3\x16\xf0@\x9bhI\ -\xc1\x84\x941\xbd\x8c|\x10B\x91\xfc\xb5\x97u\x84\xa3\ - \x1b\xe9|\x8c\xefeZ\x98M\xa5\xd3\xcb\xb8\xb8\x90\ -q\x9c\xa6\xa7\xf1.ve|O\x0b+A\x96V{\ -\xda\x14\xad\x98\x85D=\x86p\x9dh\xbeF4\xdc\xd7\ -H\xc3\x0c\xbc\xf5\xc4\x95\x90\xc4\xa5\xfd\x1a\xe4\xc2\x22\x13\ -(\x82`\x808a\xa0\xa2\x9c\xa2\x13\x85\x02\xaa\x9a:\ -\xaa\xa3\xe1I;ZA\xd5\xd4\xe88v \x85t\xcd\ -\xabK\xb0\xe0\xce|N\x8a\x0e\xc1\xf5~Wb\xd7#\ -\x8dT8h\xbbM\xe0H\xcfS)s\xf0\x89\xd9\x85\ -\xb7i\xdf\x98v&\x1d\xe9\x81\x07n\xb2\x1e\xf1\xc4\x8c\ -\x89}Pb]\xd8\xde\xcf\x08)\xf0dGB\x93\xe0\ -\xe1\x12\x9b\xb9\x0cI\x0c\x80&\xb1\xa7\xc4fn\xc3\x12\ -#\xcero\x89\xcd<\xc6HlV%^ck\xb1\ -\xc7I\xc4\x97s\xbc\xa4\xfd\x1f\xe3i\xf8\xe2\xf5\xfe\xc7\ -\xa42\x06W\xe6\xef\xc3\x01{\x1cd:\xa7\x99n4\ -\xe6\x188e\x97\xe3\xd4/.\xe6{+\x90\xec\xd4e\ -.._\xef\xaf@\xe2c$f\x1c\x92\xd8\x81\x1d`\ -o\x89\x99\x87%~}\xa9\x0b\x80\xbd%f\x1d!\xb1\ -\x88\x9dd\xe9\x84\x89\xc4\x89\xcc\xa2\x89\xc2\x18\x89a\xd8\ -,\x06.x\xb1\xb7\xc4`\xc3\x12s\xf8\xc5\xc6\x03\x94\ -a\x89!N}\xc0\xac\x99Y\xdf\x9ee;\xca#8\ -NSR\x9eX\xc9`\x14\xc5\xf6\xdc\xff\xa9\x17\x0f\x94\ -\xb4\xf6\x07\x16U\xc3\xe0\xd6\xa5\x22*A\x868\x99Z\ -\x16\xcb0\xb4\x97\x1f\xf0\xe4\xdf\xce.\xbaG\xd0:\x00\ -kg\x03\x12p\xd1\x80\x1a\xdaY'\xb9|\x9f\xe4n\ -I\xa1\x9a\x12#v\x94\xf6;o}*\x89P\x1f^\ -\xd7\x90rD\x9d%J\xdd\x1b\xb4K8\xf5\xe8\x95\x17\ -\xeb\xad\xbcR\x9d\xaab\x81\x94\xcc\xc7\xda\xaer\x100\ -S\xaa\x1f\xae\x05#\x91\xb9\xfbT\x19\xc0Cw\x91\xab\ -\x7f.7\xbc\x83\xd0\x8b\x86CZ\xb4Z\xc7\x01&J\ -/?&R&\xc7\xd8y\x07E\x90\x12\x98&P\x0c\ -\x93U\xe2e?.{\xb7\x0dYD\xac\xb5\x8d\x03d\ -~L\x9c\x1c>u\x9c\xb8\xa4!H\x85)\x0a\xb1\xa6\ -\xf8G\x85\x89~\x85\xa9\xe3\xb8\xf5\xfat\xf5\x91\x9cj\ -\xe1V\xd6\xba\x03\xb4>\xfa\xd9\xb8\xbaZ\xea\x91\xc8(\ -\xfe\xf1-\xae\x09\xf7J$uY\xb8Q\x01\x0eG\xea\ -F\xc2\x12\xd9\xf1Q\xac\x0d\x92VP(N\x91\xa1G\ -\xd1\x9f\x09\x1f\xdd\x88\x9aK\xaf\xf0\x01\x9d\x88M\x814\ -w\xad\xd1S(\x1a`\xe2U\x07\xb3$\x0bs]\xe8\ -\x15Q\x8cY\xe2\x93\x1aF)p\x9f\xf3\x8b\xac\xb3B\ -\x89\xac$\x9b\xad\xafP\x12q7\xaa2S\x01\xcaP\ -\xf9x\x1211\x1e\xc7>\x91\x11\x1b\xe5\x04\xeb\xca\x15\ -\xcdB\xa4\x01\xde=C0\x9d\x09k\xcbW\x9a'\x14\ -\xc4\x0aQ\x84\xe6GE\x81\x8f\x84\x023Q\x8bBI\ -TH\xe9\xa0\x81\xa8\xc5\xcd<*\x1aX $\xa1z\ -\x90\x8c\x85;4\xb4\xb2\xe0\x8c\xa4G\x06\x83\x89\xbd\x17\ -\x0c\xe1z\x04\x97AIQR\xac\xa6\x0b\xb4\x04\x83\xd6\ -\x11\xa4!\xaa:<\x8c\xe3\xc7\xb3C\xe4\x01\xfb\xf4W\ -(\x02\x04V-\x95I\x017\x94\xfaP\xa3\xd2TW\ -\x1fJ\x1a\x11\xca\x91O\xa8\xa2L\xe8=\xe8\x1a\xf2\x08\ -\x0b\xce\x0a\x82@\x84N\xa0\x04\x05\x98\xf9rS\x92\x89\ -KZ\xfb5\xc2\x98\xc9'\x98XD\x8cxbTH\ -9\xc1\x86\xdc\x8fv\xaeg\x5c@\xd0]\xeb3z)\ -\xe9)\x10\xf5\x99$\x17U$\xac\xcf\xfa\x15\x8b&e\ -\xd6w1q\x11\x177Z\x02<\xbf\xbez\xfb\xef\x8f\ -\xaf\x81\x7f\xfa\xbe=\x88\xffD\xc5HY\xdf!\xbf\xfe\ -\xbe\xf7W\xef\xee\xae\x7f\xf7\xdbu\x94\x19\x99_\xfe\xbe\ -\xbdZ\xa3\x00\x8f\xef~_\xbd^\x7f\xfd=\xf0V$\ -\x15\x00B\x9e.\xb4\xed\xd9\xb4\xfc\xab\xbb\xdbw7\x17\ -\xdd\xcc\xffs{u\xb3\x9e\xfb\xddU\xb3\xf4\xed\xfa\xaa\ -\xf9\xdf\xab\xe7\xdb/f\xf7\xdf\xce\xee\xeef?=\x96\ -VsW1\x88WRP\x1f\xb3k\x0d\xbb/\xc3\x9f\ -bHq6\xd1\x09yIa\xd7\xb3/\x9b\xdc\x94\x92\ -\xaen\xcf\xb9x6%\xd0\xd2\xfc\x99O\x18K`\xa0\ -[\x93\x89T\xd2\x81l\xc2Y4\xc8\x8dZ\x06DP\ -\xd2]b\x22^42\x93\xdbL)\xa1\xa9\x13\x8dB\ -D\x86\x8fy\x06I>\xb1(\xee)\x90g_w\xb3\ -\xd3J\x10\x81`\xcb\xb6f#@\x09g\x8f6\x13\x0b\ -\xabx\x9b)\x05)\xa8\xcdD/\x0cM\xaa\x99-_\ -T*h\x19]\xdaV^\x89\x82\x18\xc8]\x16(R\ -\xc2\xd8\xb8\x16\xd6\xcd\xac\x15\xfb\xba\x9b]\xc5\xf8r\x95\ -m\xc8\xf2,1\xad\xca\x92T\xc1.4\xa8V\x14\xc8\ -d\x1dF4)dN\xf4\x0c96\x99.E\xd2\x91\ -\x9f\x1aG\xf2\x83\xed\xf8\xf7\xb3\xb5\xf6\x15.\x12\x08(\ -K\xc6\x1e\x85\xd3!\x9f\xb3\xb5\xcd\xa4\x02\x12\xe8\x13\xd1\ -\x92\xe8-[\xf3\x82\x1e\x81\x13\x85\xe2\xe1\xa4\xb6\xaa\x17\ -\x156\xe7\x9c\xa8\x16\xcd\xb0hE\xc8\x92\x94\x0d\xa9a\ -\x09f\x0eZe\xb6w-\x9b\x17\x12\x80\x96p\x11d\ -Q'\xc0N\xf6\x97\x8f\xd9(n\xcf,\xb0\xcd\xf4\x82\ -\xa0\xfaT\x94g\x9bG\x85R \xde\xabVz\x11\x12\ -\xa7G\x01\x22\xb5\xcd\xc4\x02\xae\xf1,)\x7f\x18\x95\xbf\ -w\xc2u\xad\x05`\x5cy>\xddu\xc47\xb7\x17\x8b\ -v-qcJ\xe6\xf7\xf3\xe5\xe7\xfe\xf1\xbfUd\xef\ -\xc9\x92\x9c\xdc\xccW\x034\xde\xccC\x914\x04\xb5\x09\ -\x14\x11\xc8\xa0lm\xbb\xb3\x81\xa6L\xa6O\xc6\xdf&\ -\xd3\xcc\x82\x82\xe2\x93Xzj\xa0\xb1\x8b\x99w/\xca\ -\xcc\x8e\xef\x9by\xac\xeb\xd5\x22\x0a\x08\x05\xe4\xc70\xf3\ -\xac\xd5\xcc\xef\xd8\xbe\x9b\xda\xe1k\x864\xbd0a\xe0\ -\xba\x1d\x05/j\x88\xd4\xb5\xa3\x88^<\x11\xb8kG\ -\x91\xb4\xa8\xaa\xd1{\x06\x80\xb9\xa8Q\xc8\x9a\xb1`/\ -\x22\x12V\x0d\xe9*\x13S\x8c\xd7\x0ci\xcd\xee\x1a\xd2\ -\x9a\xbdn\xdb\x98K\x0a\xbcg\x05)KDp\xae\x1b\ -R\x93\xe2\xaa\xc4\xbanH\x0d\x8a\x03Kj\x97\x85\xb6\ -\xd5\x8dL\xad\x85u3\xbb\x86\xb4f\xaf\x1bRm\xab\ -\x1bn\xda\xb5\xa4\x86\x05\x02\xdc\xb5\x0b\x8ee\x81\x08\x00\ -\x5c\x07\xd2\xad\x84\x92\x06v-ix\x81 I\xaf\x96\ -\xb46\xe4\x9a!\xad\xd9\xebv4\xa1d\x90ht\xed\ -hH1fe\xadv\xd4\xb3\xb8\x09\xba\xad\x1b,\x97\ -\x92J\xae\x1d3\xeaT\x14\x15=\xab\x19]f\x8az\ -\xb2\xac\x9bQ$*\xa8\xce\xde5\xa3\x8f\xd9\x00H]\ -3\x8a\x98\xc5\x12\xc2\xbav\x14QJx\x8a\xbcgG\ -\x11\x0a @\xac\xd9Q\xe0b\x86h\xd5\x90VT\xd6\ -\xec\xe8\xae\xbeS~~\xbe\xd3\xcfe\xe5\xf3\xa3Zy\ -\xc4\xe2d\xc1\xfa\xf3\x9a\xf9Op%;\x11\xd9\xff\xf7\ -K\xb9\x8d\xf2\x98K\xb9\xc7\x9f\xaa\x99O[K\xd3I\ -'\xc8\x5c\x00\x80&\x04%\xdc\xa8\x95t\x85\xae\x16\xec\ -\xbc\xf6w\x0a\x85P\xa2>x\x8cg\x81\x9f\x84\xe9y\ -#vP\xff\xfa\x0d\xa7\xaf\x90\xbf\xdax\xca\xdc\xf7Z\ -\xe7\x90~V\xac\x7f\xfc\x12\xbf|\x9fUaU\xee\xe7\ -g\xfd\xfc\xe0O\xa9\x10\x1b\xfc\x0cS\xfa\xf9\xc5\x16~\ -\x10_\x02l\xf0s\xf5~~\x09\xdb\xf8\xf9\x1f7\xf8\ -=\x9a\xdc\xaa4\xfb\xac\x16\x03\xea\xd9\x88\x09\xb2\xc7y\ -#\x17\x8b\xc5\xd0\x81\x00]\xd6\xb6\xc7.wZ\xc4\xe2\ -bc\xe5\xc4\x88\xf5q\x06\x87\xecK5:\xee\xf9\x08\ -]\xd62\xe2\xf8\x95\xd1p\xa4\xf6H\x9c\xbe\xbb\xc4\xc6\ -\x8b\xdcC\xe2\xcc\x9fQb\x91<@\x01Dq\x10\x8e\ -\xe1Wm\xf7\xb0\xe6qo\xf1\x86\xdf\x8f\xda\x99L\xce\ -\xb2}g\xb2I\xbb1\x99\x8a\xb5\x1f\xef\xdb\x9e\x8c\x14\ -\xcd%\x93Cw'C\xbd\xfb\xa0\xad\xc9u\x1b#\x14\ -$P\xab\x11g.\xa8\x09\xack\xcfp\x1d\xa1z>\ -\x95fp}\x0f(\xb7\x9f\xa7\x01\x950St\x82X\ -\xdc&R2I\xc8\xc6\x9c,\xca1\xbc\x97\xc7\x0f?\ -\x03P\xa2\x10\x12*\xd6G&Z\xd4\xa2\x0b\x8feI\ -b\x88X\xdbe\xc0I\xf6)\xc0\x03\xa3\xe1\xf9\x05+\ -\x8f\xc7 :lx\xb0\xf2H\xa1\xf0.<\x89m\xad\ -\x1d\xac\xbb\xd4\xa0\xbev\xba\xbd\xee\xa1$;l\x96&\ -\x98\xc0(\xd1mPt\x01\xf8e\x8a.#D\x1f\x9c\ -s\xd5m:\x89%@\xa13]U/\xa0\xa1(u\ -\x02\x96\x05T\x03\xf2y\xde\xa0\x5c\x8c\x81\x86gcE\ -1Sr\xe2\x85\x15\x938\x17\xd3\x98L\xa3\x801\x09\ -k\xfb\x0b\x0b\xb9\x13\xa2L\xa6\x04\x85H\x9c\xac\xf9\xca\ -%\xd3\x8c\xf3\xf0I\x1b\xb1\xd3p\xd7\x918\x10D.\ -\xea\x81\xa4uo\xa1\x14e$\xad\x93X\xec\x92\xcc\xdf\ -'\x99wI\x06\x80\xb4\xa2\xe0\x19\x96\x8f\xd3[EH\ -\xf4\xc9\xd4\xb8\xb4\xe0\x19N\xa6L%\x1d\x10u\x14b\ -'5\xc5i\x85\x8dA;+\x97\xa2\x18\x18v\xb6\x11\ -2\x15P\x0c\xab\xbdN\xb8x&\x90\x1dh\x8b\xbd\xa8\ -\x98\x99\xf9q\xec\xb1\xed\xb2\xb1]\x0eV*T\xeb\xe8\ -\x14Z\x81\xb0\xaaR\x8e\xc5]\x89\xean\xd5.\xc5\xbc\ -K1\x1cD0\x85@i\x90\x22\xc3TX\x9e\xad\x9e\ -\x04\x98!\x13.\x9c@\x086A/\x0c\x09\xd6\x5cV\ -/\x96L\xe3:\xa5\x0d\x1bu\x8b<\xd8\xa8\xb7pd\ -pU\xb1\xb4\xc2\xceH\xdc\x89\x92A\x11\xf2\xea\x0cU\ -\x9aA\x15CwR\x89\xc7n\x08\xee\x84\xe1\x13+\xaa\ -J)6\x99JIAs\xc0Q:6l\xfd\x15\xf1\ -\x97\xa3c\xccG\xd61\x1a\xd61;X\xc7(J\x0a\ -%\xd55Jb\x85HS\xde\x8b\xc4rx5c\x8e\ -\xc5\xc8\x04\xe4@\x1d\xc3\x12b\xe4\xca\xc7\xd21\x18\xd6\ -1\xe0_\x07\xc7\x8a\x98\xf0.\x83\xe3\xaf\x96\xbf\x17?\ -\x1ck\xf9\x7f\xf9\xbd\x92c\x97^\xf9\xab\xe5\xef\xd31\ -\xd6SX\xfecz\x05G\xf2T\xc6\xe8\x18\x8d\xf4.\ -~\xf9N>\xed\xe4\xe4\xff:\xfd\xde\xda\x17IFL\ -\xbf\x07\xfa\xe2p\xe8e|\x94g\x8c\xfe\xe0\x88\xf0\xcd\ -\xe7-:\xc6IE\x1f\x1f\x86\x1d\x1f\x14\x1e\x83\x8e\x8e\ -\x8a\xe6\x9e\x1e\x9d\xf1O\x14F\xc13\xe2I\xc9/_\ -y O\x81\xce\xcfi.8$N\x19\xe3w-N\ -\xe1\xdc\xd9V\x07Q\x1cD\xb8\xcadU *\x0e\x80\ -\xa6c\x04\xb2a\x81\x92\x0f\x15\x88\xb2h0j\xe7\xdd\ -\xc6Y \x08\xbd6\x92[1[?!\x0a\xad\xa0\x90\ -\xd1\x18\xb9v\x18\xcd\xc3\x0e\x95+l\xe9\xe8zV\xe5\ -\x83\x22\x18\xa8TMNafJ\xc4G\xb9*\xcd(\ -\xb9h\x84E\xfeT;\x15|\x9c\xa7G\xc1E\x11C\ -\xad\xbbu\x89\x04\x11\xb3\xba\xafV\xc8\x05\xb53\x89\x84\ -\xa2\x16.8|\xf2\x06QF\xda\xd3\xfb\xff\x1d\xc0\x01\ -'S\xa2\x92\x14\x99\xb6\xfc\x8a\x9e\x181bq_p\ -\xfc\xfa\x9chO\xc4\xec\x84\xa10)\x9a\xec\x150\x91\ -\x82\x99a^\xb5\x8c:$\xf3\x0d\x92y\x87d\xf8%\ -\x14f@*\x13(\xda\xa8S\xd1 \x01\xf5\x89\x14q\ -\x0a\xf5\x98 Iaso\xb0\xc3\xc8Bi\xce\xa3\xb0\ -\x93a\xec\x98\x0f\xc4\xceKZt\xb0\xe3(\x22l\xdc\ -\x85\xae*\xda\xfa\xe5y\xbd<\xd4/\x059Z@\xa0\ -\xa0\x01\x9bf\x03\x9f\x00'!O\xb8\xb0\x87\xa1\xb5!\ -1(\x98,\x18\xcb\xd7\x9d \xaa\xe68\xb5\xa3a\xbb\ -F\xf6iE.\xba\x07\xb9\xc8\xa83\xb5\x83a\xe7\xa8\ -\xc4x\xcd\x89,\x8a\xeeI]\xd5\x09\x85\xc8\xba\xd8}\ -\x9df\xde\xa5\x19\xd4!\xcaTamu\xc8\x15(1\ -&X@\xdd\x89\xad\xf9\x16L\xc0m\x97\xc3\xa5M\x83\ -\x9cL)\x8a\x90\x1b\xe0\x18\x15\xa2\x1d\xfcM\x8e_-\ -\xd7\x07\xb1\xb3],\xd7\xaf\x0f@z\xf1\x93]\x1e\x80\ -\xfc\x8a_/~\xf4\x09/\x1d\x18a\xd7\x09v\x09\xc8\ -\xff\xaa\x17}z\x811\xe2\xa1\xcfg\x88\x9f\x1e\x1b?\ -\xfb\x84\x97K\x9c\xde\xadB9\xce3\xd7\xfaF\xa9\xee\ -K\xa3\x1e\x8d\xd6\xb7\x8b\xab7\xdf6\xf4X4\x83L\ -Wp>\xee\xc9\xca\xc4\xe7-t\xcb\xdd\x9f-]\x02\ -'\x03\xae\xea\xb6\xa4\x13\xc54\x05\xdfZ\x95\x8dWT\ -\xf5\xbfd\x8a\xe4\x83/\xa8\xc2\xc2\xe2\xa6\xabh\xd1V\ -\xa1\xeb\x1b\xaaz\xa4\xb7\xc0\xf7\xa4G\xf5\x14\xeaJ\x0f\ -\x1e\xe9(\x86\xeb\xe2\x93\x88\x07u\xc5G\x22N<\x9a\ -\xf8\x16\xdc\xf3~.&L\x94\xf1\xd2+\xf3\x91\xea\xaa\ -\xac=M\x05\x06\xc0\xaa\xe3+\xcbiG\xaa,g\xf4\ -\x00+\xb9_E\xd7\x0d\xd5\x90\xf5\xc4`\xc3U\xb0h\ -\x8aE\x8c\xd2|1]\xd9P\xa3e\x1c\xc4\x0bi\xec\ -\xea\x04\xd7\xa3\x0f\x88R\xbf\xc1o\x9e\xc7\x80\xa9\x81G\ -a\xad\xd3j\xb6,JBO\x1b/I\xb9\x00F\xdd\ -\x18\x5c\xefy\x1e\x17\xc8\xb4p\xf6\xbc\xc17F\xeeN\ -\xea\xd9\x9c\x84p\xf8N\xad\xcaw\xf6\xea\xbb\xab\x8b\x7f\ -o6\x9d?\xfc\xb5\xb9\xb8gE\x879\x14\x1d\xc1c\ -P\xac?-?;\xed\xb1\x1c\x1eyB&Ss\xa9\ -\x0f\x87\xcc\xa5\x86\x0f8\x8a;s\xd4\xd0|\xd6\xa0|\ -\xbdz\xf0\x13\xa1D\xf9\xa0\xf2P\xa2\xf5\x03@\xd6\xa6\ -\xa1S\xa2{\x14\x88\x12\xa3\x9f5c\x9b\x86X\x17D\ -\x92^\xfe\xb4E\x83\xe4\xa2M\x83\xfc\xd9E\x01\x00\xfb\ -\xcb\xa0\xfe2\x14\xda4X\x86\x10Cff\x7f\x19\xb2\ -\xa5\x0co\xd3p\x19l\xb1\xbd\x8c-\xcdl\xd2\xa6\xe1\ -2$\x07\xe4\xd8\xd2\xde\xaem\x1a.\xc3@{\xf9\xf3\ -\x96\xf6\x8eY\x9b\x86\xf9;\xe2V\x19xK{\xcf\xb8\ -M\xc3e\x04\xcaV\x9d\xe2\xfe\xf6\xbe\xbf\xba\xfe~q\ -7\x5cD\x22n/\xc2\x06_\x1f>\x5c\x86\xf0v\xa8\ -b\x0bT\x97M\x1a.C\xd1%<\xa2\xb7\x0c\xd9\xd2\ -\xe4)m\x1a.\x83I\xb7\xca!\xb4Em\xe7m\x1a\ -.C\x153s\x8b\x1c\xb2\xa5\xfbi\x9b\x86\xcb\x08Q\ -\x01\xf3\xfe2\xb6\xb4\xb9R\x9b\x06\xcb0\x08\x03\x00\xea\ -/cK\x9b\x0b\xb7i\xb8\x0cA\xdb\xaa\xbb\x8a\xfde\ -p\xb6i\xb8\x8c\xa4\xd0 \xea/\x83\xb7\x94\xc1\xc3B\ -8\xa7omp\xd5\xd1c_dDfz\x7f\x19\xbe\ -\xa5\x03\xda,fC\x9d\xa3\xe7\xdd;\xe3\x0e!\x19\xf6\ -~\xeeV\xaf\xa4T1\xeb\xac]\xa4\x92ikk\x17\ -\xa3\xb8\x93R\xdf\xb2\xc1p\xfb\xb0O\x13\x1e[\xac_\ -\xb6\xe9P\x9f&b\xdba\x18\xde\xa6\xc1v\x05I\xe9\ -\xe7O\xfd\xfc_g\x9b\x06\xf9#H\xf6\xf3\x97-:\ -\xb3h\xd30\x7fa\xc2$\xee/\xc3\xb6\x94\x91M\x1a\ -\x96\x81P\xad\x9f\x7fl\xd3\xfb6\x0d\xf3\xb7\xb4m\x83\ -B\xe4\x96v6o\xd3\xb0o\x99\xb1\x85?\x1d\x81\x7f\ -n\xf5]#\xb7\xf9\x95\xd2\xa4]|\xbe\xe0\xed8m\ -ik\xc16\x0d\x97\x91\xaa\xdb\xcb\xd86\x8f\xd06\xed\ -\xe0\x04dDj\xbf-\x05\x18m\xaf\xd5!M\x89\xfb\ -\xcb\xa0-e,\xda4\x5cFJ\xf4\xf3\xdf\xd6\xde\x97\ -m\xdaap\xd6\xed\x0e\x00lio\x876\x0d\x97\x91\ -\xbc\xdd\x07\x87\xd8R\xc6\xeb6\xed0>kl\x1d\x9f\ -\x11\xb6\x94q\xd9\xa6\xe12\x06lH\x22\x8d\xd4)<\ -U\xec\xc1#\x0b\x12\x81o\x89@\x98{\x09w\xc0c\ -\xc6!\xc2\xa8g\xcc6\x19=\x99\x07\xd82h\x98\x8d\ -6\xb8\xc8\x12\xfd\xfcc\xcb\xc0Mm\xdaa\xe2\xc8\xfd\ -\xc6\xdca\xcb\xa07\x1b\x9e\xf9\x1a\xe5\xd6\x1e\x11N[\ -\x0a\xc06\x0d{\xacJ\xb1uD\xf2-\x8d\x9c\x17m\ -\xfa\x84=V+@,u\xe9\x9dbA`\xa5\xf7<\ -V\xe6\xe8\xf5XUz\xb4_m\x8b[\xf9\xbaM\x07\ -{\xac\xbaE1)\xda\xb4\xcb\xe8\xc9\xdbF\x850\x18\ -\xd1\xae\xa3O\xb2K9\xe4\x5c\xb7\xb4\xdd\x03\xe7\xec3\ -\xa1\xf9\xee\xa7\x9c\xc5\xee\xb1\xf3\xd7\x97\x97\xba\x11\x93\x1f\ -q\xc8\x19K\x1c\x80\x06+\xec\x8e\xc6\xbc\xa93~\xa0\ -\xca=\x9ci\x9f3\xce\xe4\xe2\xa8`\x18\x1e\x02\x86\xf1\ -\xee`,..\xebK<\x07U\x83M{\xd0\x18f\ -=\xfe\x00<\x0e;\x04\x8e\x88S\x9d\x80\xc8\x09?\xe7\ -y\x80\x00\x07\xc0!@{h\xc7\xffk\xef\xda\x9a\x9c\ -8\xb2\xf4\xf3\xfaWt\xf0F\xacH\xce\xfd\x82YG\ -@\xdbLL\x84\xbd;\xb1~\xd8G\xa2\x01\xc10\x03\ -\xb4\xa3il\xe3_\xbfu\xaa$\x95J\xa8\xbaQ\xd3\ -=`\x8f%\xe3V~Yu\xf2\x9c\x93\xe7\x96\xa9K\ -\xea\xf3\xe7\x07\xfc\x1e \xc8\x8c:fI_\xa7:\xc8\ -\xae\xa2\x0e:\xc0:\xca\xbf\x0fP\x07\xc3a\xa1\xe3z\ -\xd5\xc1q\x15u\x08\x1c\xa2\x0e\xc2\x03\xd4!\x07ER\ -\xc2\xebUG\xf2U\xd4\x91zS\xb1C\xd2\xffE\xb1\ -c^\xe6\x19])\xf0\xe5\xe7\x92\xebs\xdc]\xef_\ -r\xfe.+|\xc4\xa9\xd2\xc2)\xfc\xf4b\xca\xf8!\ -e\xff\x04\xdbP\xbcJ\x9aU\xe4\x83\x5c\xe5\x04\x9e}\ -\xacm(\xce\xa4\xd9y\xd2\xd7\xe8*fy\x05u\x98\ -\xe3M\xb9\x8a9\x7f\xa9\xaeb\x91\x97\x9e\xce\x0d\xcf\x9f\ -\x07\x1c\xe8*\x96X,\x1fN\x19.s\x15K\xfe\x04\ -\xdbp\xbcJ\xcd\xe1H7e\x1b\x8e\xf2\x19K0g\ -\xbe\x8a:Xo\xaa\x04s\xf6\xc3J\xb0k\xfeIf\ -\x06\xcc\xdf\xe5O2_\xfd\xbc\x8f\xf1\x84\x04\x7f<{\ -\xac\x82_\xcbI\xaew0[\xd6\xa3~\xdc\xe4\xf6\xf8\ -\xa5I\xd8:P\x1d\xa91\x88o\xf6}8\x9bj\xe8\ -5\x89\x18\x17\x88\xe8\xb7\xae\xe3\xc0\x87pbY\xffB\ -A``P}\xab\xa7\x19\x07\xd5\x17a\xad\x05[=\ -\x0f\x93\x7fz\xa2\xe8\xd5\x15\x90\x8fgS\x81\xe7\x01\xe7\ -M\xa64\x96q\xbb\xbad\xe8\x80\xadcq\xc3\x9a\xb2\ -\x8e\x87\xe2\xa6tm\xb3k\x12\x03a^\x8e\xc0C\xe4\ -\xd0-\x11x\xc2}\x98N\xb8\xaf\xf65q\x8f\x17p\ -\xcf\xd7\xc0\xbd7J\x1e\x99\xd7j^\x17\xeft\x01\xef\ -z\x08\xef\xb6\xc5\xbb\x8c\xbcG\x03\x00\x1a\x99\xb7\xbe}\ -\xddg\xc8:\xfa\x9c\x18N\xbca\x92\xbd!b\x1an\ -xUn\xcc$\x80\x1b\x8e\x11\xb0\x83Ds\xc5\xf3x\ -\xcdGL\xe5\x90\xad\x9e-\x9f\xbf\xfd\xe6\xfex\xd6\xdb\ -\xc9\xeb\xe5\xb3\x9f_.\x7f\x99\x88\xf8\xcb\xcb7\xcfN\ -\x7f\xb9\xb3\xfe\x88\xbe\x85\xdf\xda\xd7\xbf\xfe`>\x10\xed\ -d\xb2\x93\x17\xcb\xee`\xa9\xee\x9a\xff\xba\xb5\xa7k\x95\ -A\xfbwr\x06\xbe\xdf\xbd\xec\x8e\x9c;}\xb5\xec\xe2\ -\xdc\xd3eQ\x5cw\x9cuC\xec\xc1O\x9f\xfc\xa3\x9b\ -\x82}=ON\xcf\x9e-\xcf6#\xe0\x04\xee\xb3y\ -7C\xd6?\xfa\x8e\xe2g\x0d\x0fE\xcff\xb2\x9e\x9c\ -\xbc]N\x99\xff\xed\xf4\xb4\x0b\xc1\xd4T\x89\x90u\xda\ -9|~\xbb)\x09\xa4L\xbb*\xdc\x1a5\xf5\x88\xa0\ -\xbd\xaa\xac\xd8l\xb0\xb7\xeb}\x91\xf5\x1d\x82\xef\xce\xce\ -\xbay\xbe\xf3\xea\xe4\xfd\xb2\xe3\xfc\xfb\xfa\xf3x\x95\xc0\ -\xff~\xfaK\xa9\xad\xb2\xe1\xbb\xe5\x08\x95\x8a\xdf\x8e\xe0\ -H\xab\xef\xb9\xf3\xe4\xc9\xe9\xaf\xab\xde\xad\x12\xa6\x08\x8d\ -u\xd3_\xba\xd6\xa3\xb3\xd3\xd7\x7f;[\x82\xd8\x8f\xcb\ -\xf3\xf3\x97o^\x94\x03\xd4c\xa8\x17~}_\xb7\xac\ -\xa0\xa1\x90\xa9\x1a\xe3\xa7_'\xd0\xfbm\xe8m7W\ -\x1d\x9d\xd2^33\xdf\xedx\xffA\xc7z\xbe\x00\x00\ -j\xbezp\xf9\xfa\xa7\xbd\xf8\x96\xb1\xd1x\xe9\x16*\ -#\xba\x1a\xb1\x1bp\x8d\xbd9y\xf2j9\xea\xb2\x1e\ -?\xbf|\xfb\xb2\x03\x07lp\xaa\x0f\xdd\xe9\x9b\xaf\xee\ -\xbf^\x9e\x9f<;9?Y\x9b\xd3\xba\xdd\xed\x18|\ -\xf5\x1f\xf7\xdf\xbc\xbd\xf7\xf6\xf9/\xdd\xab\xe1\xe5\xab\x97\ -O\x97o\xab56{\x17_\x17\xbc\xa5\xc4\xe9\xd7f\ -(\xc6/\xc7\x8c\xcd\xfe\xce\xbf~\xdb\x01\x8c\xe6\xc2\xc4\ -R,\xd60w7\xe3LF\xfd\xf1\xf4\xdd\xd9\xd3\xe5\ -\xc3:)\xee\xed\x15\x86,\xd7\xea\x1c\xf1\xf5\xf7\xcb\xe7\ -\xe7\xff\xd3O\xee\xa8\x98\xd50\xa7?\x9dw\xa7\xce\xfd\ -\xd6\x7f\xe5ce4\xa3\xa8\xe7'g/\x96\xe7k\xb8\ -\xe8M\x91\x92e`\xa3\xfb\x06\xc8\xf2QW\x03\x9dt\ -\xac\xfc\xed\xbf\xffB24J\x9b+Z[h\xddP\ -\xd5`7\xa3\x9d\xa3\x94\xffmfp\xf8&IM\xea\ -\xf3\x93W+\x0f\xef\xee8_\x1e\xaf\xcc\xe7Q\xff\xe8\ -\xf17\xa7?lz\xc6\xeb\xcby\xea,\xbd\x93\xa7#\ -\x95\x9e\x8b\xd2\xf2\x94\x8f\x82\x0b\x1c\x05\xbd\x82\xfchi\ -\xceJ\x8a\x7fDE\x14\xe8d\xff\x9c#\x9b\xa1\xe8\xbc-\xc6\x838\x9e\ -\xd0\x13\x5c\xd3\x9b\x99{J\xb3\x88\xf9\xf9\xaf\xe7\x94\xa2\ -\xcf\xcf\x1a\x1b\xcf\xcf\x7f=\xa7\x94\xf2\xba\xe6_q\x96\ -\x922\x1e\xc2\x93\xf2,\xa5\x10\x99\xf7\x93\x87\x1f\xe8I\ -u\x86\x12\xce\x12\xc1\xcemyJ\xc4\xe7\x0c{f+\ -t\xdc\x0f\xb9\xf9M\x8f\xbdj:`\xd6\x8c\x0e\xd5P\ -\xb9\x8b\xf2\x94\x88\xac44\xd1\xc7\xa5\x1aBx|c\ -\xfb)\x9f\xae\x98\xbc\x06\xc58^M1\xf8\xf8\x86\xb6\ -j>Y-n\x87\xaa\x85\x93\x1frL\x89\xc4\xd5\xd4\ -B\x8fol\x17\xe80\xc5D\xc4\x83\x98\xa6\xe4\x98\x0f\ -Z\x00N\xb3\x94\x8e\xe3\xbb\xc8)%\x9d\xa7d>\x1b\ -H\x1f<|p\xfc`\x87\x92_P\x04\x89\xcfQ:\ -\xd6c;\x96)\xa5\x0b\xd2\x04\xa2\xce\x16A\x0f\xbb\xe7\ -4\xb8\xe7|\x9a`@\x9a\x0f\xee\xf5\x9cR\x9a\xd7\xb8\ -\xe0\x05\xa9\xcb\xbb\xa7M)\xcdk\x5c\xcds\x96\xd2\xb7\ -\xf5\x9cR\x9a\xd38\x1e\xe0c\x993\xeeq\xff\xc5\xfa\ -\xb2\x17lR\xbf\x1aP?M0n\xd8T\x8b}\xf3\ -\x8d\xe0\x0e\xf9\xe1\x88\xb9E\x00\x18/\xd0\x1axB\xe0\ -\xd1\xf7\xdb(f\x03\x8b\xf0\x0e\x0dn\x9a\xb6\x86\xd2\xbb\ -c\xf1C\x1a\xd2\x16T\x80\xb1v@\xb4P/@\x1b\ -`\xdf\x96\xa0,2\x88\xde\x92e\x11\xd6\x1c\x1dX:\ -:\xd5\xcf\x90\x85\xb1Pa\x85(-B[0O\x01\ -\x86\x02\x8aR\xdde\x884\xc3\xfc\x88\xee\x1c\xc0\xbfy\ -\xd3\xaeEj$\x08\xf5\xa7\xee\xaf\x7f<`\xf7\x08\xfe\ -ai<\x9c\xd8?\xbcfw\xba\xbd\xde\xea\xda\xf7\x83\ -\xd1\x82f\xe6\xeb\xf3\x1eX\x1d\x85\x17w([j0\ -s\x7f\x9e<\x13*\xcb\x9a\xcc\xb8kt\xfalY\x0b\ -\xcc.v=]=j\xc2\xfb\xc9\xbc\xfc\xd2kbj\ -N_\x0c\x16\x92,\x87\xea\x8b\x04oO\xec\xaeF\x02\ -`\x5c 7\x05a\xb6\x9a\xba\x11%l&l\xd4\xa1\ -edN\x03\xe4\x1a6\xd8\x9d\xc36\x14\xda\x08\x87\xdb\ -\xc4d\x00:\xbc\x07 2\x8a:\x124.\xc3\xf36\ -P)\xc0!\x0b \xc4\xbe\x99\xd4\xdb%\xd0V\x0b\xbd\ -\xb8\x18\xda\x88\xba\x9f\xe3\x11-c\x9b\xf8\x9ci\xca\xc1\ -38z\xacl\xebM\x1b\x97\x08\xf5\x07\xa3\x1b\x7f\xd5\ -F\xc4\x96\x99=\xe2\xc50\x94v\x90\x8e\x8e\xab\xed\xb0\ -ik\x94\xa8C\x0b\x9bG\x16\xa2akD\xb0\x1c+\ -\xa9%\xca\xc2c\xa5\xa8\xe4F\x91\xd5&\xc4j\xe9\xc2\ -{\xb2\x9b\xd7\x83\x8e\x92K\xc1\xba\xcb\xde\xaa\xbd\xeb\x85\ -e5\x83\x91LKg\x22\xd7\xdb\x1f\xa7\xb0\x19\x9di\ -n\xeb,+\x85\xe3\xc2[fh\x19O\x13\x83 Y\ -#\xd1\x80\x90\xdd\x17\xd1\x84\x93\xd9\xb6\x91\x0c\x18D\xf1\ -\x96.@\xb1@\x88\xc6\x8a\x14\x1d\xa9\x09\x18\xc9\xa4E\ -\x1dY\x5c;(Kz\xec\x90Tr\xc5\x0dR\x8a\xb1\ -\x16\x89.\xde\x832\xcc\x13J\xa2\xe2\x80T[\x00\xcd\ -\xa8oSjra\x1a\xec =\x86H\xcc\xa5u\xa8\ -!\x89k~ j\xd2\x0b\xd24\xb2\x9a\x9aH\xb7\x02\ -\xbc\x1a&J\xce\x9b&\x87\x83\xea@\xc3\xccc\x91\x0d\ -P\xb2\xb4\xb4F\xa2\x89sZ\x14@\x94\xcc\x1d\x02\xcc\ -\xc8t\x14\xd9,\x12bD\xbe\x9f*\xfb\x80\x19\xa7\xdb\ -;Q\xa47\x80\x17\x93hv\xd9\x9b\xfa\xeb\xec\x87\xe9\ -\x8aX$w\x17T\x87X\x8f\ -t\xb8T\xa3\x7fe\xd5WS\x8cu\xc7\x91G\xdd_\ -i\xb0\xc8USch\x0e\xfe4\x1dx\xd0\xca4D\ -XA\xfbC.~=\xaam\x9a\xf2\x89\x12\xcbQ>\ -VT\x8f,\xa0\xf7ao\x22\xb2H\xe9E\x95A\xd4\ -H\xef\x11+Q\xfbln\x83\xa8%\x0ch\xddQ\xb2\ -\x11b\xc9V\xe4\xae \xaa\xf3\x15E\xe5CD5\xe5\ -\x02X\xbcD\x0d\xf0Ej/\xaa\x968%\xfc\x80\xd4\ -\xff\xab\xd1\xbf\xb2\xbe\xafD\xad;\xfaY\x15\x1ffU\ -\xf9\x0a\xa2\x06\x5cQT\x9d\x11u\x87\x85\xe3] \xa1\ -\x0dqG\x8f\xb2\xa6\xac.A\xc0\x0e\xcch\xba\xcb\xf0\ -\xae\xe2v\x81\x8cF\xbd\xadG\xactSq\xce:p\ -w\xdcis\x8f&\x10pW\x15\x17\x88\xef\x83\xf8w\ -_\xec\xad`Yp;\xba\xbb-\x08*>\xd7\xdf\xbd\ -\x01\xe7\xcd\xe9\x9b\xe5\x10\x5c\xce\xde\xbdZ\xde[\xfe\xbc\ -\xec\x92H}\xf0\xf3\xec\xf4\x9f\xcbM\x0d34\x87w\ -\xb1\xef\xe1O\xbf\xae\x81\xe2\xae{\xf7\xf3\xde\x93w\xe7\ -\xe7\xdb\xd8?\xba\xdf\x95\xea~_\xaa+nV\xe84\ -t\x15\xf7\x93\x82\xbc\xd6\xfes\xa1\x8c\xfa\xbc\xe0\x0b\xe4\ -\xfa\xab\xd0\x99\xfag\xa8\xf7\xae\xbb>\xfe\xf7]g\x8c\ -\xc6j\xaa\xa3+\xefv\xf9\xbfME|\x03\xcb\x89\xcf\ -\xb4\xfc\xba\xbc\x98as\xbd\xfd\xc7\xaa\xd9\xa7f\x9b7\ -\xa12\xdf\xad\xff\xfe\xac\xe0\xbf\xa8\x0a~\x9c\x7f\xc3\xcb\ -\xe7\xbf\x1e\x9b\x02~\xcc\x81\xc6\xb7>\xbe\xa0\x1f\xed\xe7\ -\xc0\x02\x8a\xcd\xb3w\xc0]\xc6\xf5\xd6\x97\xb9\x1c(M\ -]]\xd8\xc0\xbd\xc2\xfa\xad\xcf\xb1 \xb8qay\xaf\ -\xb0y\xebK\x5c\x12\xcc\x0b{\x81\x80\xbaO@\xc7[\ -\xbf\xcb\x85\xc0\xa4\x8e\xff\xfc\x95\xf9e\x0b\x87\xa9\xcey\ -\x87\xfb\xb1\x93\x0c\xa8\xae\x1f\x08\x916&3\xb3E\xc5\ -\xcfI\xb3\xc8~\x09\xab\x91\xaf\xb6\xde\x1c\xe8?%\xba\ -\xf3\xe5\x90W'O\x96\xafV\x9f\x1c=\xaa\xbe}u\ -\xd5\x9d2*\xa8g\xd5\x0f\xdcx\xd1\x07\xec\xa8\xe5\xea\ -\x10\xe9\xf7\xdf\x86\xfdMU\x8aV\xb2\xad\xf2\x0b\x9b\xa3\ -\x98\x84,\xef\xd0d\xe7\x86X\xf4\xb2\xc0\x9f\xa0dL\ -\xf8\xf5T\x9d\xa3\xc0\x17h\xd78v\xb5[\x14M3\ -\xd3>J\xc7g\xf5\xd1\xc85\xda+\xfc\xd5\xcb\xee\xcf\ -=Yc\xcfN\xba\xcf:\x9f\x9d\x9d\xbc\x1fX\x1b\xd0\ -\x8b\xabYe\xbe=\xf5\xef\xf0\x16U\x80\xd0\x10\x9a\x00\ -R\xd2\xbbf\x03\xb2d\xad\xd8\x8cn\x08X\x183\xa7\ -\x0f\xf5}5\xc3\xb5\xac\xb0Z\x06\xd5\xeah\x904U\ -@\x91\x85a3\x16\xa4\xec\xb0\x85Q\xf3\x100\xee\x1b\ -\x15$\xea\xaf6BM\xf6\xed\xbb\xac\xb1i8\xae\xe8\ -zC\xaaQ\x861Q\xab9\xa4\x0c\x07H\xe3\x0e\xb3\ -\x06aX\xb9\xb31\x93\xb0\xf7Xy\x8eV\xd9\xe1\x18\ ->\xdc\xaa\xc9=\x8f\x89\x0dA\xc1\xeaBiINZ\ -E-\x00\x98P\x87qS\x0e\xc2\xa3\xacF\xe7h\xf5\ -\xa2\x88#\xe9\xd0\xf0h`C\xa3\xb7P\x1b\x84\xaf\xd1\ -(\x13\xd8\x17\xae-\x91\xc3K\xa7f\xae\xc6\x059\xa7\ -+\x17\xf3\x14H\xfd\x8d\x026\x08TiO\x92\xb9J\ --pr\x89Z\x9ef&k\xd4\x80b\xe8U\x0e\xc8\ -\x22\xa0\xf4Qu\x1d7\x1b\xe6\x80e \xb40\x19\xfa\ -HV\xecVC\xaa\xfc\x1c\x89I\xf6\xf2\x81o\x06\xb5\ -\x85B\xaf\x87m\xe6\x14\xd7\x0a\xdb\x08\xb1P\xea\xf5*\ -6\x8aZ\xd8\xa0\xff\x84\xd6\xdf\xb5Q\x93dK\xf6U\ -\x83\xb3Y\xac\x1bX\xb1}P\xb9\xa0/j\x16\xd0Q\ -\xa9\xa6F5\x8c:\x8c[x\x04a\xd1\xf5\x9e#\xe4\ -a\xf6\x0b\x01\xe0p\xee\x8d\xd2\x11\xa3&?\xd5\x99\xca\ - Z\x9a\x91\xf3h\xdb{\xf6\x89X17\xe0\xf9%\ -'\xb1v-tv\xaeS.\x1a\xe4\xd0q\x07\x1b\x22\ -\x06\x92\xdc>4\xf9\x12\x1b\xdc\xfe\xe8XR\xde=\x0d\ -$\xf1/\x09 \xd3@QN\xe3 Y\xe5zH\xaa\ -\xac\xd6\x0e\xa8\xa8\xde\x17\xfa\xc8i\x89e\x19u\xa1\x16\ -\xc6\x22\x8e\xa9\xbdk\xba(vP\x92+A\xbf'\xe0\ -\xe5\x10\x84}\xf40\x16\xc0\xb2S\xb4f\x04\x99T\x01\ -D\x14\xdd\xec\x08u\x08\xe7\x5cq\xc4\x12$'\x98\x94\ -\x1d1y\xdd=\xa2\xda\x18\x01\xd5\xb7)ZS\x89\x08\ -\xdf\x1e\xdb\x1b\x9b\x19\x97en\xd8D\xcc\x06\xce\xc8\x83\ -S{\x04K.\x90\xa0!\x84D)\xc3!\x01{\x88\ -\xd2@\xfa\xd5\xad (\xf77\xab\x07\x944)\xc5\x0f\ -B\xa1\xd1\xc2\xa0\x0a`k@.\xa4\x1dV\x83s\xac\ -03\xf3>P9X\xd4jmDK\xd1\x09\x046\ -AC\x9b \x80\xc4\x04\xf5l(\xe1\xa8\x13T\xa2\x85\ -brN\xd1\x8au\x8cN\x13\x94\xbd\x05\x82\x88OP\ -\xa4F\xc9AYr\x15\xeahT\xee\x86\xe4lz\x94\ -\xda\x14\x1c4\x16X\xfe\x19\xa1Z\xf2kbT\x81\xdb\ -0-Hu\xd8107G+\x13\x09\xf14\xaah\ - \x22fe_\x12F\x94\xfbL\xee\xb7\xa3\x1f*B\ -\x19\xf41\x82\xb0\x91\x04A\xc94\xa2BMTSK\ -&\x8b\x96\x92\x01\xb2\x85\x1e\x17J\xe9\x06\xba\x85\x9a7\ -wR\x89\xc2\x92\x8d\x93Gl\xa2\xa7\x11\x1d\xf5\x7f\xbc\ -\x8dZ\x03\xb0\xa0\x18\xc7)L\xc2\xc9|\xe4h\xc4\xb6\ -\xb9\x9fA\x11\xac\xb9\x92\xf4\x097\x1a\x91\xa0\xe0\x96\xe5\ -\x7f\xbfO+\xfb\xc2\x1e\x03]\xbe.\x0a\xfdz\xf6\x0d\ -46\xfa\x92C\xd7|\xedc\x81\x97\xd4>\xae\xa99\ -\xd4>\x12\xe6\xa5R\x06\x02\x95\xc2(8%\xaa\x0e1\ -5\xd2\x82\x5cWz'-\xa5{\xac2_%\xa0\x9e\ -\xb0V\xd7PpD@jT\xa63\x0f\x8f,L,\ -I\xfbL\xe7I\xa8yC\x99\xce\x14o0\xd3\xcd\x17\ -\xcd\xf3\xc9O\xfeL~\x7f&\xbf\x9bI~!\xcd,\ -\xc5\xae7#\x16\xda\x81\x98\x7f\xe6\xc9\x7f\x93<\xa9\x90\ -c\xac\xdb\xed\xb4\xd0\x9d]2\x1c3\x89\xe3\x87\x99\xc4\ -\xe1\xc3Lb\xb9\x93I\x1cv3\x89\xebn&q\xfd\ -0\x93\xb8~\x98I\x5cv3\x89\xcb\x07\x99\xc4\xe9\xc3\ -L\xe28f\x92\xd9\x18\xcf\x17\xd6\x07\xf6\xbb\xaf\x0f.\ -\xcf\x93w\xc6D\x89\xd8\xdc\x16H\xd1\x22\x5c\xf8*\x8b\ -\xc18Lc\xd7\xbdO9\xda1I\x93\x95\xb7\x80s\ -y\x8b7\xab\xcd7\x17a\xce\x0d\x00\xd1H5\xb0\x03\ -\xa6\xb7\xecq%b\xe2\x02/|'\xe7\xf2\xba\xe2\x22\ -\x93s\xf8}\x9a\x9c\x13\xed\x94\xa4\x88\xadwx\xcd\xf5\ -6\xca\xf6\xe6Z\xacv\xc9pks-\xc6\xbd\xb5z\ --\xfde\xd0?p\xdca\x1bo\xdd\xdaa\xcb\xed\x0d\ -6\xe4\x06\x8c\xd4!\x9b]\x1b\xd4\x86j\x05mv\x93\ -\xa6\xfc\xed\x0b\x9c\x0e\xfa)^\x94\xcdBh\xde\x8b.\ -0\x02\xfaR\xbch\xd7i\xe6\xdcj\xf4\xa2\x01\xf0h\ -\xae\x98\xa1k@\xb22\x14\x1a\xedz\xe2\xfe\xa5\x1d\xc1\ -\x9c\xd6\x0e\xde\xc0\xde\x1f\xa8\x5cn\xefh\xad\x01\x8a\xb3\ -\xfa\x15\xb57\xef\x1a\x9c6u\x8da\xdb\xb0\xf6:\xa9\ -\xdf\xe5\x9c\xee\xce\xc2\xf0Wb\xd5a{\x15\x94\xf2\xd1\ -\xa6\x09-\x093E\x17w\xb21\xd8P\x05\xb6L\x12\ -\xb2\xbc=_\x1bD\xea'\xf2}Csg\x9fa\xee\ ->]\xd1\x87gS\x8f/9\x0e(c\x09h\xdc\x12\ -\xd9\xb8\xf6\xc1\x19\x1b\x93*\xf5\x8b9w\x12\xa7#\x8e\ -\xa6\x12\xea\xd1\x87pN\x009\x12k\xa0\x89\xe6\x1b\x8c\ -\xab\x9e\xc6\xa6NA\xb8\x85\xba\xb6d \xcc\x1a\x87\x0c\ -3t`F\xb3%\x84\x13_-\xc2D\xfa\xe1\xf3\x11\ -\xf0\xc5\xceGXs\xac\x9cG\xd0\xd8\x83p\x1b\x93h\ -!\xe2\x93\xeb4\x1b\x1bR\xe4P\x8a\x130\xc1\xb0.\ -\xb7\x00\x8f\x9a\x0c0T\xebS\xa6\x81\x22\xf3\x91hs\ -\xa3D\x1f\xb1\x9a\xf4h\xc6(l[(W\x0eL\xd7\ -\x1e\xc3 \xef\x98$m\x14\xe1Z3\xc9\xa9\x80\xf2\x07\ -\xb0\xa3D\xb8\x82\x1d\xd1\xef\xcc\xaf)\x1a\x12e`_\ -?\x99\x1by\xcdQ\x88\x93da\x96AaG\xac\xcd\ -\xd4\xb5\x90$\x02\x8b\x9aKjh)\xe2\x85:\x889\ -\x17=W\xa0,\x885\x89\xcb8\xa4\x0f\xa5\xfd\xb8B\ -\x22\xb1Z\xe1z\x14Je\x8e\xe9\xa0\x13T\xb29$\ -jNP\x83\x06J\x1e}\x01IM\x18\xb2\x0c\x11\x1b\ -9\x93\xe2\x91{\x0b\xc2@*sgB3\xe9\x17\x90\ -\x988\xc8\x8d\xac\x0e\xabO\x88\xa0B\x9a\xd4\x95\x14\x0a\ -)G\xc1\x0dB\x98\xa2\xa7\x08FAW\xb0\xa4\xa3\x1f\ -\xf6+\xcb\x9b\x00)p\x99.\x90\xa8\xea\x11gC'\ -\xd5,\x0c\x01\x1d\xfdH\xa0\xa5:\x04\xf7\x98\x92Z\xef\ -6\xd9\xaa\x97\xa2PH\x87\xa4\x81\x22*Y\x8f1\xb8\ -\xe9\x9e\x91\xf7Zvn>\xcbQ\xff\xea\x87`\xbe\xf9\ -\x7f\xaf\xd7Z\xe4TM\x01\x00\ -\x00\x00\x17\x91\ -\x1f\ -\x8b\x08\x00\x00\x00\x00\x00\x00\x00\xed=io\xe3F\x96\ -\xdf\xfbWp\x9d/i\xacH\xd5}\xb8\x8f\x01f\x82\ -\x04\x016X`&\xc1\xcc\xb7\x80\x96([\x1bY2\ -$\xb9-\xf7\xaf\xdf\xf7\x8aW\x91,Q\x94,\xbb\x03\ -\xc7v:\xa6\xaa^]\xaf\xde\xcd\xaa\xa7\x8f\x7f\xdb\xdd\ -.\xa2/\xd9z3_-?]\xd0\x84\x5cD\xd9r\ -\xb2\x9a\xce\x97\xd7\x9f.~\xfb\xf5\xc7\xd8\x5cD\x9bm\ -\xba\x9c\xa6\x8b\xd52\xfbt\xb1\x5c]\xfc\xed\xf3\xbb\x8f\ -\xff\x15\xc7\xd1?\xd6Y\xba\xcd\xa6\xd1\xc3|{\x13\xfd\ -\xbc\xfcc3I\xef\xb2\xe8\xfb\x9b\xed\xf6\xeer<~\ -xxH\xe6Ea\xb2Z_\x8f\xdfGq\xfc\xf9\xdd\ -\xbb\x8f\x9b/\xd7\xef\xa2(\x82q\x97\x9b\xcb\xe9\xe4\xd3\ -E\xd1\xe0\xee~\xbdp\x80\xd3\xc98[d\xb7\xd9r\ -\xbb\x19\xd3\x84\x8e/j\xf0I\x0d>\xc1\xd1\xe7_\xb2\ -\xc9\xea\xf6v\xb5\xdc\xb8\x96\xcb\xcdw\x1e\xf0z:\xab\ -\xa0q6\x0f\xdc\x01Qk\xed\x98\xb01c1@\xc4\ -\x9b\xc7\xe56\xdd\xc5\xcd\xa60\xc7PSF\x08\x19C\ -]\x0d9\x0c\xear\xb7\x00T\xec\x9d\x8c\xab\xf5G\x07\ -\xf4\xdf\xc1\xbf\xaaAY\x90lV\xf7\xebI6\x83\x96\ -Y\xb2\xcc\xb6\xe3\x1f~\xfd\xa1\xaa\x8cI2\xddN\xbd\ -nJ\xec7\xc6ml\xc92\xbd\xcd6w\xe9$\xdb\ -\x8c\xcbr\xd7\xfea>\xdd\xde\x0050\xe3>\xded\ -\xf3\xeb\x9bm\xfdy>\xfdt\x01\xeb\xe3\x82\xe4\x9f\xcb\ -\x19\x5cVtD\x12\xcer\xd0\xa2[\xbfJ\x98\x84F\ -k\xab\x15q \x0d\xe2kt7]Mp\x86\x9f.\ -\xbe\xaeV\xb7\xf1\xea~\x9b\xc0\xa8_\x9b\xfdB\xe9\xdd\ -\xfd\xf6\xf7l\xb7\xcd\x96y7\xb00o\x95\xae\xda\xb5\ -K\x1aK\xac:\xc8vw\xab\xf56\x9e\xcd\x17Y>\ -\xd8\xf8fu\x9b\x8d\xef\xe6KX\xf6z\x05\x0f\x93\xcd\ -x\xb5{\xbc\xce\x96\xf1|\x02\x946\x86v\x8b\xf4j\ -\x91\x8d\xd3\xc9v\xee\x0an\xd3\xc5b,\xccN\x98q\ -5\xd5\xbb\xe5up\xa0\xdd\xf4\x0e6\x96r\x19\xac}\ -\xacj?C\xf5\xc7i6\xdb X\x8es\xfc\xc4\x05\ -%\xae\x0ej\x81l\xb2t\xfd\xd3:\x9d\xce\x81Yr\ -8\xaf\xcb\xc9j\xb1\xc8&\xb0o\xe9\xe2!}\xdc\x5c\ -T\x00\xd0U\xb3)\xb7T\x15\x9dB\xb7\x9b\xed\xea\xae\ -\x84\x85\xdd\xd8>.\x00+X\x18C\x8f\xab\xf5\xe5w\ -\xf4\x8aI\xce?\xb8\xa2\x15\x90\xcf|\xfbxI?\x5c\ -\xd4mV\xb3\xd9&\x83\x81\x89W\xe6\x88\x06Z\xc0X\ - R\xc6O\x1b\x8d\x84F\xa3\xc1\xd1\x18\xa9F\xfb8\ -n.\xfbiht\x5c{y\xb3\xce@\xca|\xf7\x9f\ -_\xfe\xe7\xe7\x1f~\xb7\xbf\xc7\xaa\x07\xcd\xccP#\xaa\ -\xfa\xeb\xa2\xf4\xb7\xe5|\x0br\xe4~\x93\xad\xff\x85\xbc\ -\xf8\xbf\xcb\xdf6Y\x07\xea\xd7u\xba\xdc\x00\xe3\xdf~\ -\xba\xd8\xe2\xe3\x02D\xef\xf7\xcc&Vq%\xed(\x06\ -\x92I\x94b\xf2}=?\x0a(\x11\x168\xd1\xaa\x1a\ -5\x8fPjd\xc2\xb9\x10\xa4\x9e\xcb\x8e\x01\xac\x14\xd8\ -\x83\xf1`\x99\x0f[aq\x0f\xbe`\xb9\x01,\x0c[\ -%\xce\xd5\x8a\x84\x0bn\x08o\xcc\x95\x12\x06\xc5B7\ -\xa6jT\x22\xb9\x94\xd66\xa6\x0a\x1dHD\x86\xd9G\ -\xca!\xb2\x0c\x10\x9c\x9c\xe0o\x0fy\x97\xc4\xa5X,\ -\xf7\x93r\x09\xa5\x85f\xf1\x81!\xb3\xd9\x8cd\xb3!\ -\x1c\x95p\xca\xadPz\xd8\xc0$\xa6\xfd\x03\xa7iz\ -\x95\xdaA\x03k\x06RB)\xbb\x7f\xe0\x10#\x86\xf0\ -\x0b['\xe9\x10\xfc\x0a\x0f\xbf\x7f\x06\xe6=\x96\xac\x03\ -\xcc{\x9bn\xd7\xf3\xdd\xf74\xb1\xf8c\xe8\x88\xc0\xaf\ -\xca?Y\x09|\xccXB8cz\x14+N\x81!\ --\xb3/\xc0\xd38\xd1t\xf1,hlv\x8dh4\ -gC#\xe0\xca\xfd\x18\x87\xc6\xe2\x13\x05,\x0a\x05\xeb\ -\xe6\x14\x1f\xa9\x86\xd5r]#q\xb2C$\x22\x00\xf3\ -$\xc8\xe4\x11\x0d\x1c\x82]\xd0\xbat\x16\x84\x9d\x05a\ -\xd7\x9f.\x10\xb1`\xf9\x1c\x12\x95G\xa1\xb5\xd9\x87\x16\ -\x96\xf5S\xa9%OG\xef\x1e\x15c\x90\xf0(o\xd3\ -\xa3N(Umj\xd4Fs\xd3\xa6F\x93hi\x0c\ -oP#\x85!\x18\xb7\x034\xcc\xf1\x86\x8d\xc3\xd6p\ -Sc\xe6~N4l`,q\x94a\x13\x1am\xb0\ -a\x03\xa3\xa9C\xb2\xf1\x1cL\xdd\xc2'\xa5\xa6\x9f\xb9\ -=\xea\x1cF}\xc8\x8dF'\xb2\xc1\x88`\xcc\x18-\ -\x89'\xcd\x90\x11\x8dM\x8c\xa0\x86\xcb\x06#\x828\x94\ -Z[\xdb`D\x0a\xa0\x9a1\xd2\x9dMW\x88\x10\x90\ -\xbeT\x12.\xc5\x08|(\xca\x09\x98\x1b\x84\x8f\x8aG\ -\x94-\x08 \xa4\x02i\xc2\x13\xcb\x15e\x9c\x8f(\x05\ -\x8dH@\xb6\xbe\x1fb\x19\x05\xd08\x9cP\xa6|6\ -\x19D(A\xb2\xa4\x9e\x05\xbc\xdfV(}\xbbV\x87\ -\x89\x94 E\x89\xc7\xc8\xc1\x19^\xa539k\x99L\ -$a\x80G`w\xf6a\xd0\xf8*8\xbe\x96R1\ -\xea\xedcp|\xc3\xafdv\xd5\x1e_X\xb0&-\ -\xeb\x1b?h\x9e\xe8\xd9D\xb5\xfb\xd2F\x08\xc3\xb4\x19\ -\xcc\x9f\xa0\xc8\xbf\x01\x7f\xc2\x0c{\xf8S3o#\x9d\ -\x1a\xd4 \xbbA\xb3\xd1\x06\xf7\x09\x96(\xa9%k0\ -_\x17t\x16\x02\x05\xde\xe3,\x91\x94i\xd5uuB\ -v\x90\x06\xfa\xe29\xbbY\x0e\x8c!\xb4\xe3B\x0b\x9e\ -\x00\xb0\xdc\xc8\x01X\xc58\x9aF\x14\x14\x856\xf0\xa8\ -$\x10&h\xf6\xf7\x03\x85\xcd3h\x16\xc4\xf4p\xa2\ -\xca\xcd\x94S5\x0b3=,\x9c\x0bKBR\x036\x86>\x84\ -)!\x19\x89i\x98\xb4\x0f\x90U\x96\xc1>?\x89\x8b\ -\x05\xfe\x0e\xe1b\x91\xbfS\x08i\x02i\x15\x8a!~\ -x\x9b`\xa9,\xb6q\xe8M\xe3\x81\xad\x9a\xa9\x99\x9c\ -\xc9\xf3\xd0\xf3S\xecFK\xfb\xce\x08\xe1\xcb\xc6\x86\xa5\ -\xa68*\x0b?t\xefB|x\x22E*\xaf\x14M\ -\xb5\x00,\xcbc&j\xf0\x89\x99g\x8c\xa1\x1c\x5c\xba\ -\x17\xb1\x1c\x1eX|>\x04\xeds\xec\xf0\xb5\xb0;z\ -%\xd0\xcb3\x18_1D\x1f~\x89\xff\xac\xa8\xb3G\ -\xa2\xae\xef0\x03\xe3D+ \xc4\xf6jc)\x13j\ -(\xd4\x8e\x18b\x12\x96m\xde?\xd3f|Kd\x0a\ -\xfe\x5c\xc8\xa4\xc5i\xb3\x06.1l\x07\x980\xf4U\ -\xe2R\xbf(.\xe3\x22\xe0I^%2%}v.\ -\xe7\x0dlZ\x89\xe7J\xa9}}\xc8\x14D\x9e\xf2\xc6\ -\xed\x95,\xfd\x8c\xc7\x90{U\xe2+D\xdd\xf9\xc2\xc4\ -\x7fuE+\x88:\xc3A\xe37E[\xe0\xf2|o\ -t\xff\xea\x8a\x16\x90y\xbe\xc0\xfd\x9b\xa2Uo\xda\xe6\ -d\xd4\xbdi\x9b\xf3!S\xbfi\x9b\xf3\xe1\xf2M\xdb\ -\x9c\x11\x99o\xda\xe6\x8c\xc8|\xb6\x80\x83\x87L\xa1\x12\ -*\x99\xb2|\xe4.B\xbeN\x1e7/ /kD\ -\xc6\x94\xe3\xcb\xce\xd7\xc9\xe1\xe6\x05\x22\x8a\x1e*\xad\x82\ -RA({\x95\xb8<\x9f9\xb9\xdf&\xf2\x90\xa9\x11\ -?\x8a\xda\xd7\x89\xcc\xf3\xbd7\xe8\xd8\xe61\x1e\x1d\x02\ -\xe98\x8a\x0d\xae\x14P\xc8_#\x0a\xcfx\x94\x9a$\ -\xdc\x08E%7m\x5c\xeaDZ\xa6\xa5\x10#\x87\x1e\ -\xf2:5\xce\x19\xcf`\x0eAeI\x97\xe65\x1aB\ -\x94\x9c\xd3\xaa\xdc\x87Lf\x12\xcb$\xfc\xbcf\xba\xa4\ -\xe4\x9c*\xe70*_9]>\xff\xab\xea\xbf\x8c\xb7\ -C\xe99\x95\xcf_\xdc\x0f\xa7\xf4\xd8\xf3'o\x01\xa2\ -\xfd\xb8|\x8b\x5c\x9e\x0f\x99\xec|\xef\xfdi\xd2\xc5\xa2\ -I$\xe7\x94Z\xf3\xaa\x83\xe9\x94\xfde_\xf8S~\ -,\x01\xbd\xd8\xc9\xba:O\x14\xe8\x16w\xbfiD\xbe\ -\xe5Q:\xc0\xd5\xf9b5!f\xa3\x0c\xd9\x8dP\xf6\ -R\xcc\xf6q\x8cY?\xddS\x95\x17\x15\xf3\x94N\xbf\ -\xcc\xb3\x87w\x15\x1a\xae\xd2jIw\xe9u\xe6N\xcd\ -\x02\xf2\xf2\x1b\x1eE\xc5\xd5j=\xcd\xd6e\x95r?\ -\x8d\xaa\xe2`m\x9d\x89\xd5\xdb+\xec\xb5\xaa'\xe1\xfa\ -\xcdM:]=|\xba`\xedJL\x8a\x0a\x0bk\x17\ -\xe3\xd5\x0b\xa1\x13\x0b\x9ec\xa7\x09^\xc0\xd0\x98F\x16\ -\xdc=\xda\xae\x9c\xae&\xf7\x98\x1f8\xbe\xcf\xb7\xf6n\ -\xd7i~\xbf^#\xc0\x22}\xcc\xd6\xcdl\xb5u>\ -[c\xaa\x86E\xc6[\xafds\xb3z\xc8\xd1\x82d\ -~\x9f\xb5G\xc0z\x7f\xd1\x1e\x0cV]\xafq[B\ -\x0d\x1f\xe6Kh\x10\x979v\xa9\xec\xac\xbd\x80(\xa7\ -\xa9M\x07s\x05\x04\xe0O\x8a}\xcd\x1f\xf1^\xbe?\ -\xa3\xfb\xf94\xdb\x84\xe7\xe4\xea\xe2\xab\xab\xd5.\x5c\xbf\ -\xba\xfa?`\xd4\xf8.\xdd\xde@\x0f\xb3t\xb1\xd9\x07\ -\xb2\x5c\xb9A|\x90\xbcf\xbbZd\xc0b\x93\xac>\ -\xeb\x8d8\xf2\x8bM\x83\x1a\x8b\xad\x0b\xe2~\x99\xde\xc5\ -\xd7\x8b\xd5U\xba\xe8\xc5\xf1m\xba\x9b\xdf\xce\xbff\xd3\ -\xfa(}\xb3\x0fo\xc5\x85\xc8\xaaQ\x02\x93+\xd9r\ -\xfb\x88Y\x94w\x8fX\xd6\x90>X\xc0\x84\xa8-O\ -\xcc\xa6<_^\xef0\x83\x05\xf2WENU\xd5c\ -\xa0*\xbb\xbd+jk\xe6\x89\xa2/\xf3\xcd\xfc\x0a\x0f\ -\xc2{+\x04\xd8%\xe6\x1e\x9e\xb6Jq5\x05<\xce\ -\x09\xa5\xe2f\xb5\x5c<\x16`\xa5,\xe9\x8a\x10W~\ -\x9bm\xd3i\xbaMkyR\x96p\x90\xa8%j\xd6\ -\xd3\xd9\xe5?\x7f\xf8\xb1:\xe3?\x99\x5c\xfe{\xb5\xfe\ -\xa3\x9cB\x14!@z\xb5\xba\x07\x9a\xad.=`\x12\ -\xe3\xc9%\xca\xd5t\xfby~\x0b\x0c\x83\xa9\xb1\xff{\ -w\xbb\x00\xc9VU4\x80\x11\xdbu\xa7y\xb7\xeb,\ -O}\x1d\xcc\x16>\x9d\xdc\xce\xb1\xd1\xf8_\xdb\xf9b\ -\xf13\x0e\xe2\xddE(:\x9do\x17\xd9g7f\xfe\ -X\xaeb\x5c,\xa3\xbcI\xe0\xad\xf2\xe3\xb8D\x83\xfb\ -t]\xa3\xe7Z\xab\xeav\xe86\xa4\x0eM\xa2\xf16\ -'S\xa3\x18\xf3yf\xb1z_\xa2\xf1\xba\xa2\xaaP\ -xA\x08\xa6\x18z\x1a\xa0}\x0cxj\x98\x01\x8bY\ -\xc9a\xc0\x91\xab\x96V\x13\xcc\x13Jt\x02\xda\x87\x83\ -\xed,y\x22\x89\xf0#\xdc\xf9\x14%\xf1R\xe0\xac\x81\ -\x05k\xa4\x167,\xea\xf4fVSB\x85d\x1ff\ -\x80\xc1K\xc0\xed\xf7-\xc5\xebr\x17\xbfw\xd5\xde]\ -\x99\xcdv\xbd\xfa#\xbb\x5c\xae\x965)\x16\xf7\xf2`\ -<-\x94\x7f\x83\xa5\x90x\xad\x1b\x99\xb58\xc6$\x22\ -.\xd7\x97W\xe7rIX\xc0\x00o\xe4\x87\x03\xba\x8e\ -\xa5H\x80\xf5t\xa3\xab\x10J5\x81\xb5\x81\xa6\x1ey\ -\x8fq\xb8\x98\xf8\xd6K\x07i8(\xa8+\x85\xf1\x9f\ -\xf6\x14U\xa2\x98R*\xb0*\xeeR\xbd\xb2\xc6\xfd\xa4\ -R\xf6c2*\xc5;7\xa3s\xdc\xe9@\xce\xbd\xbe\ -\xddQ\x03w\xe7\xcc8B\x9d\xe0\xcd\xb3\x140\xb9\xc8\ -L\xd7\x93\xbeE\xe0\xec[\xa9a\x8f 1\x1cY\x0b\ -\xd3\xb8$U\x0e\xefn\xe7KD}\x83\x9c\xeaz\x97\ -\xc1\xd5\x09b\x13\xaa_\xef\x02\x84Z\xd7>\x06jQ\ -jFTj\x8c\x0b\x88Q\xd9y4\x8901\xad\x15\ -R\xc8\x08d\x017 \x18Fy\xe3(\xce\xffV\x1f\ -s8`\xf4\xaa\xa6lQ\x17\x14\xb0\xb0\x0d\xd5\xbfE\ -TT\x92\xe8kh\xba\x9bm\xban\xdf&+\xeb\xb2\ -%\xcc\x9b'TP\x8c\xab\x9f\x8fL\xd0\x1d\xa0\x84\x80\ -C\x10\x03sh0\xe3\xa4\xed\xe1\xad\x93\x04\x92%G\ -\x0b$\x1b\x10Hn\xa3\xa4\xd9\xcb\xbb\xfe]\xdf^\x89\ -\x14\x10\x0e\xe7d\xb6\x8f\xe3\xeb\xe2\xc1\xe7\xb9\xee\x084\ -\x91\xda\x12\xa6\xc0\x17\xc3\xc4\xba\x8c\x0b\xc5p\x80\xea\xb9\ -\x06\x88a%\x12s\x01\x80\xc2\xe1\x12<\x1b\xc6\xbc\x10\ -LN\xd1\xa0h\xb8flT\xe5du$Mq\x8b\ -$\xa5\x22\x8aMb@'\x19\xd0Te\xd2\xd5(.\ -\x1e\x1bEe\x0b$\xef\xaa|T67\x8d\xc2\xba\x1d\ -\x19\xd5-\xa3\x12\xd6\x07\x08\xb6\xf2\x07+\xe7R\x0d\xd4\ -\x9d\x5c\xcd6\x0d\x1e\xeff\x91m\x08\x88\x9e\xeap\x12\ -\xdb\x86xj$\xbd\xad\xe4\x99\x9fI\xf9\xb0\xbc\x04\xa5\ -?\x84\x03\x02R\xb9\x22\xaa\x1es\x04\xdc@%-\x11\ -.9\xa7\xa2`b(G\xa9\xd5s\x0d\x10S\x8e\xc9\ -9\x15\x03\xd3D\xa9\xc4p\xa2E\xc7 !\x0a\xd3\xff\ -\x0c\xd3\x1c\xb5_\xe1.\x88\xfa\xcc\x5c\xd6 \x1a\xee\xc1\ -\xd7\x88\xf1\x8e\xb9\x00S\x88\x89\x10\xd8j=\xbf\x9e/\ -\xd1S\xf8%\xa2\x86\xe2\x97\x0c\x80\xa0\xb8-\xda\x00\xb6r@\ -Z\xd7v\xe1\xbd\x11\xb0\x0c\xc7\x8f];\xa3@\xfcU\ -E^\xffnR\x96\x10\x85\x82\x1a ]\xe6\xa3\x1a \ -\xee\xb6\xf9\x1a\xdd\xe2\xbc\x12\xdc\xa6\x09lL\x82\xe9\x91\ -ARD\x0c\xb3t#,R\xa8\xa2\x04i\xba,*\ -\x1f\xb0e\xde\x02j\xdd\xd91Ex^\x8d\xa35\xe0\ -\xb1\xc0\x01s\x87\xb1\xbc(?pf\xa9\x22\xb4.*\ -\xa0q5\x0e\x9e[`\x0a\x04s_\xa7P\x8d\xed\x83\ -\xe7\x04s@a\xc30T\xa3\xd7\x95G\x03\xab\x8fT\ -b\xde2\xc9\xe8H%L\x1a\xe2\xbe\xaaa\xafi\x1c\ -\xea\xd9PM\x99\xa6\xb6\xe8\xb9\xfa\xc8\x81Z`i\xb0\ -\x070\x7fp#\xad\xd5\xef{h\x0b\x5cL\xcb+\xe2\ -\xb2`\xe6hG\x5c,A.B\xda\x92\xb0b.\xea\ -\xa2\xe2oNY\xd8@\xa9\xa8\x04\xab*\xdb\xc0^\xdf\ -X\x84\xe2\xc7\xd1\x954\xa0\x89\xb8WV\xf7^\xcc\x07\ -\xd4\x81\xa3+L\xe6m\xea\xfa\xb8\xdb$'+\x0e\xa8\ -\xc8\xe9\x0at\xa85\xd4\xd1\x15~y\x00\xd8\xd5 (\ -\xc0\xd91\xc5g\xf7\x7f\x5cF\x01\x88\xf4\x8b `p\ -\xba:\xd7O\x5c\xc3\xe6\x9f\x1d\xb4\x16\xd4\x11TQ\xea\ -\xd2-\x19\xc3\x0d\xc8,\xaf\xb0j\x84\x8b)\x9a\xf1(\ -\x07\xa5,\xaf\x97Q\x13\xbeIR\xb5\xafB\x02\x09\x1d\ -j\x93W\xe2\x11\x1a\xe0\x98\x0f{\x85\x95 \xbd\xfa>\ -$\xac\x04m\x0a\xab7\xad\xf8\xa6\x15\x036\x14&\xfa\ -\x07\xa5bI\xd0\x1b\x0e\x9bd{%\xdd!\xf1\xa4Y\ -G:I\xbeW6i\x16\x15 \xfd\x82\xc9\xf5\xda\x92\ -K\xd2*\xd1#\x97\x94\xe4\xaa\x90K\x82\xc8\xa3\xe5\x12\ -\x88\x98\x86XB\xfe\x0d\x89%0\x91K\xb1\x04*\xef\ -\x90X\x02\xe9\x16\x10K\x8ah\xd6+\x96$\xd5&\x17\ -K\x92\xcb\xa3\xc4\x12k\xf8\xbe\x87#Mvp\x1c\xf0\ -M\xd8\xbc\x09\x9b\xb0\xb0Q\x5c`\x16\xc43\x08\x9b\xa0\ -\xdb\xca)\xd8g\xbc<\xbcS|\x8ae\x82\x01 \xab\ -\xac\x0b\xab)M4\x97}f\x15lo%\xb5\x18\xb8\ -\xb4\xb4\xb0\xd8\x99c)\xea\xde\xde2\xc1\xbd\xb2\xf2!\ -\x97]\xd8\x06\x0c\x9f\x12\xb0\xae\xed\xc2{#`Yn\ -\xdf\x82\xd5\xc7\xd1\xae\xca\x0b\xbc\xbe\x8b\x09\x19\xa9\x9d\xf1\ -\x0c\xc6\x9b\xb5\x1e@\xdcnQ\xda\xea\xa5\xa9.\x0dJ\ -.\x8eiM\x8b\x95\x08\xc1\x98TuQ\xf9\x90\x1b\xea\ -`\xe0Y+\xdcX\x02_\xb1\xbb\xea\x5cP\xfa\x0d\x5c\ -I\xde\xbd\xcdE|^\xe8\x16#@\xce\x00\x87\xd4e\ -e\x8b\xdc\x5cwmh\xe4\x00yQ\xeb&\xd0\x80w\ -%a!f\xfbm+j\xa1\xa9\x11\xfbm+60\ -\x96\xf2&\xd6\xde\xc4ZX\xaca\x0c\x97\x11\xdb\xf8\x92\ -\x9ds\x1aQV\xd5\x01\x04\xf0\xc2\x88.\xc4Q\x19?\ -\x00\xebM0U\x17\x15\x7fsY\xe4\x1a\xb8\xe8\x01u\ -\x01\xbb\xb2\xb2\x0d\xec\xf5\x8dER\x15\xb6\x14\xb4\x82f\ -^Y\xdd{1\x1f!s\x1f\x8fJKM]\x1fw\ -\x9b\xe4\xe2\x88\xe5\xec\xef\x04\x12\xac\x90\xe9\xee6\xc9U,\xc9c\ -\x17N\xc5j\xed\x8e\xe0c\xf7\xc5b\xa4\x95\xda\xc8\xb2\ - \xff\x93\xabX\x84\xa5y`^\xe2k\x04\x8e\xf9_\ -K{\xbf\x00\x16\x95\xb9\xaf\xdd\xb7$\xb8:Q-\x06\ -\xfe*)\xfc\xc2\xaaM\xae`\xa1\x95\xb4*r\xa0\xf8\ -u\x9bE\xd7Q\xa3\xc1\xa0\xe8\xbc\xe4\x92\x19U\x9e\xd5\ -\xad>\x02E2%-\x87\x8dH\x84\xb2Bks\xe8\ -]z}\x19\xd0\xc4\ -Q\xc0\xaa\xc1\x0b\x8a\xbd\xca\xa0\xb3\x8c=cX\xd37\ -\x06\x1d\xa0pj\xba\xab\x96X.[$\x86\x83\x91\xc9\ -[Xj\xcb\xa5\xe37\xa4\x09\xee\xb8\xb1!\x16\xf7\x8d\ -TL\xccQ\x84\x86\xff\xbaK\xd0O\xd8\x92A\xd6C\ -\xc0\xa7\x18!\x07\xe5\xfe\xc4\xfb\xeer\x0f5x\xc2|\ -\xe5Q\xdb+\x0e`\xb1\xc1\x04l\xef\xdex\xdb\x17\xda\ -\xda\xb84\x17\xcc\xc1\xddm\x0ctpk\xabkS'\ -\xa1j\x88\xe9\xb5g\xa7\xe2\xa3\xf76>\xc3\xe6\x12{\ -\xdc\xe6\xdaa\x9b\x1b\xe0\xdd\xee\xfez{\x18\xdc\xe2\xfc\ -&js\x8b\xdb2v\x9f\xf4\xf6E*\xab\xf5_\xb9\ -\x12A*\xb5\xb0O\xe2\xb1\x135\xcfy\xb6w \xe7\ -J\x22\xf6o.\x19\xf4\xf2\xd0C\x89\x0a o\x80T\ -vN9\xf3\xa4\xb2D\x7f\xd6\x04\x18\xf7\x04\x0a%f\ -\xc8}\x8az\x11\x92\x9c&~\xca9W\x8b(V\xb5\ -G\xb5\xd4\xb5{z8Q\xb7\xc0\x02\xd8\xe9\xecL\xb4\ ->N\xdd?M\xb2\x94V\xc1\xc9\xd6\x19\x90\xa8\xe8g\ -\xd8\x1e\xeb,\xc6o\x8cT\xc6\x17(\xb5\xc4\xe8\x11(\ -\xc2\xef\xa4I\xbb\x076-h\xa0\xc1\x1a\xd4Sx\xf4\ -Tk\xa0W\xa4<\xb6\xc9q\xd7v\x09OZ*%\ -\xfdR\xb3w\xa9\x94\x0c\x89\xb0\x1d\x10\x86\x03\x85\xe70\ -\x02>0\xdd!w\x17=\xd4\x84T\xcf\x10\x9b\xb6\xa3\ -\x16k2=\x8by\xce\x8f1\xcf\xc1\x9a\xd7\x03\xd6\xd1\ -\xf6`v\x01\x8b,\xe0\x11\xf5;\x85\x87\x9c\xa9\xce\x08\ -\x87h\xb8\xe5Xxk\xb4O\xa0\x0b\xce\x8f#\xe3\xde\ -\x95v&\xa0\x8d\x0b^\x1c\xebp\xd7k\xab\xbe\xf1\xcc\ -\xd74\x9e\xe4l \xcd\x13\x0f\x1d\x05X\x8aW\x9c\xf3\ -G\xbc\x1f\xff\xf9\xdd\xff\x03\x0b\xb0\x0a\x0c\x99\x9b\x00\x00\ -\ -" - -qt_resource_name = b"\ -\x00\x05\ -\x00o\xa6S\ -\x00i\ -\x00c\x00o\x00n\x00s\ -\x00\x06\ -\x07\x03}\xc3\ -\x00i\ -\x00m\x00a\x00g\x00e\x00s\ -\x00\x16\ -\x02\x1b\xe1\x0a\ -\x00g\ -\x00o\x00-\x00n\x00e\x00x\x00t\x00-\x00v\x00i\x00e\x00w\x00-\x00p\x00a\x00g\x00e\ -\x00.\x00s\x00v\x00g\x00z\ -\x00\x11\ -\x04\xf3\xa4*\ -\x00g\ -\x00o\x00-\x00n\x00e\x00x\x00t\x00-\x00v\x00i\x00e\x00w\x00.\x00s\x00v\x00g\x00z\ -\ -\x00\x15\ -\x01\x09v*\ -\x00g\ -\x00o\x00-\x00p\x00r\x00e\x00v\x00i\x00o\x00u\x00s\x00-\x00v\x00i\x00e\x00w\x00.\ -\x00s\x00v\x00g\x00z\ -\x00\x0d\ -\x0e\xb9\xa6*\ -\x00z\ -\x00o\x00o\x00m\x00-\x00o\x00u\x00t\x00.\x00s\x00v\x00g\x00z\ -\x00\x12\ -\x0al\x90\xca\ -\x00d\ -\x00o\x00c\x00u\x00m\x00e\x00n\x00t\x00-\x00o\x00p\x00e\x00n\x00.\x00s\x00v\x00g\ -\x00z\ -\x00\x1a\ -\x01d\xbbJ\ -\x00g\ -\x00o\x00-\x00p\x00r\x00e\x00v\x00i\x00o\x00u\x00s\x00-\x00v\x00i\x00e\x00w\x00-\ -\x00p\x00a\x00g\x00e\x00.\x00s\x00v\x00g\x00z\ -\x00\x0c\ -\x009l\x8a\ -\x00z\ -\x00o\x00o\x00m\x00-\x00i\x00n\x00.\x00s\x00v\x00g\x00z\ -" - -qt_resource_struct = b"\ -\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ -\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ -\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x10\x00\x02\x00\x00\x00\x07\x00\x00\x00\x03\ -\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x010\x00\x00\x00\x00\x00\x01\x00\x00\xba\xe2\ -\x00\x00\x01\x81\x8a\xd9\xf0\x94\ -\x00\x00\x00|\x00\x00\x00\x00\x00\x01\x00\x00J'\ -\x00\x00\x01\x81\x8a\xd9\xf0\x94\ -\x00\x00\x00\xf6\x00\x00\x00\x00\x00\x01\x00\x00\x89\xa4\ -\x00\x00\x01\x81\x8a\xd9\xf0\x94\ -\x00\x00\x00\x22\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ -\x00\x00\x01\x81\x8a\xd9\xf0\x94\ -\x00\x00\x00T\x00\x00\x00\x00\x00\x01\x00\x001K\ -\x00\x00\x01\x81\x8a\xd9\xf0\x94\ -\x00\x00\x00\xcc\x00\x00\x00\x00\x00\x01\x00\x00x\xec\ -\x00\x00\x01\x81\x8a\xd9\xf0\x94\ -\x00\x00\x00\xac\x00\x00\x00\x00\x00\x01\x00\x00c\xbc\ -\x00\x00\x01\x81\x8a\xd9\xf0\x94\ -" - -def qInitResources(): - QtCore.qRegisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) - -def qCleanupResources(): - QtCore.qUnregisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) - -qInitResources() diff --git a/examples/pdfwidgets/pdfviewer/ui_mainwindow.py b/examples/pdfwidgets/pdfviewer/ui_mainwindow.py index e4215769..9ed525db 100644 --- a/examples/pdfwidgets/pdfviewer/ui_mainwindow.py +++ b/examples/pdfwidgets/pdfviewer/ui_mainwindow.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'mainwindow.ui' ## -## Created by: Qt User Interface Compiler version 6.4.0 +## Created by: Qt User Interface Compiler version 6.7.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -21,7 +21,7 @@ from PySide6.QtWidgets import (QApplication, QHeaderView, QMainWindow, QMenu, QMenuBar, QSizePolicy, QSplitter, QStatusBar, QTabWidget, QToolBar, QTreeView, QVBoxLayout, QWidget) -import resources_rc +import rc_resources class Ui_MainWindow(object): def setupUi(self, MainWindow): @@ -123,7 +123,7 @@ class Ui_MainWindow(object): self.splitter.setOrientation(Qt.Horizontal) self.tabWidget = QTabWidget(self.splitter) self.tabWidget.setObjectName(u"tabWidget") - sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) + sizePolicy = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.tabWidget.sizePolicy().hasHeightForWidth()) @@ -152,7 +152,7 @@ class Ui_MainWindow(object): self.splitter.addWidget(self.tabWidget) self.pdfView = QPdfView(self.splitter) self.pdfView.setObjectName(u"pdfView") - sizePolicy1 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) sizePolicy1.setHorizontalStretch(10) sizePolicy1.setVerticalStretch(0) sizePolicy1.setHeightForWidth(self.pdfView.sizePolicy().hasHeightForWidth()) @@ -179,7 +179,7 @@ class Ui_MainWindow(object): self.mainToolBar.setObjectName(u"mainToolBar") self.mainToolBar.setMovable(False) self.mainToolBar.setFloatable(False) - MainWindow.addToolBar(Qt.TopToolBarArea, self.mainToolBar) + MainWindow.addToolBar(Qt.ToolBarArea.TopToolBarArea, self.mainToolBar) self.statusBar = QStatusBar(MainWindow) self.statusBar.setObjectName(u"statusBar") MainWindow.setStatusBar(self.statusBar) diff --git a/examples/qml/tutorials/extending-qml-advanced/properties/doc/properties.rst b/examples/qml/tutorials/extending-qml-advanced/properties/doc/properties.rst index 9337ad2a..16924cdc 100644 --- a/examples/qml/tutorials/extending-qml-advanced/properties/doc/properties.rst +++ b/examples/qml/tutorials/extending-qml-advanced/properties/doc/properties.rst @@ -75,11 +75,11 @@ The class contains a member to store the celebrant object, and also a list member storing the Person instances. In QML, the type of a list properties - and the guests property is a list of -people - are all of type ListProperty. ListProperty is simple value -type that contains a set of functions. QML calls these functions -whenever it needs to read from, write to or otherwise interact with -the list. In addition to concrete lists like the people list used in this -example, the use of QQmlListProperty allows for "virtual lists" and other advanced +people - are all of type :class:`~PySide6.QtQml.ListProperty`. +``ListProperty`` is a simple value type that contains a set of functions. +QML calls these functions whenever it needs to read from, write to or otherwise +interact with the list. In addition to concrete lists like the people list used in this +example, the use of ``ListProperty`` allows for "virtual lists" and other advanced scenarios. Running the Example diff --git a/examples/qml/tutorials/extending-qml/chapter5-listproperties/doc/chapter5-listproperties.rst b/examples/qml/tutorials/extending-qml/chapter5-listproperties/doc/chapter5-listproperties.rst index 90cb4110..a98f18c8 100644 --- a/examples/qml/tutorials/extending-qml/chapter5-listproperties/doc/chapter5-listproperties.rst +++ b/examples/qml/tutorials/extending-qml/chapter5-listproperties/doc/chapter5-listproperties.rst @@ -15,8 +15,9 @@ have a ``slices`` property that accepts a list of ``PieSlice`` items: :lines: 4-32 To do this, we replace the ``pieSlice`` property in ``PieChart`` with a -``slices`` property, declared as a class variable of the ``QQmlListProperty`` -type. The ``QQmlListProperty`` class enables the creation of list properties in +``slices`` property, declared as a class variable of the +:class:`~PySide6.QtQml.ListProperty` type. +The ``ListProperty`` class enables the creation of list properties in QML extensions. We replace the ``pieSlice()`` function with a ``slices()`` function that returns a list of slices, and add an internal ``appendSlice()`` function (discussed below). We also use a list to store the internal list of @@ -31,13 +32,13 @@ slices as ``_slices``: :lines: 75-79 Although the ``slices`` property does not have an associated setter, it is -still modifiable because of the way ``QQmlListProperty`` works. We indicate +still modifiable because of the way ``ListProperty`` works. We indicate that the internal ``PieChart.appendSlice()`` function is to be called whenever a request is made from QML to add items to the list. The ``appendSlice()`` function simply sets the parent item as before, and adds the new item to the ``_slices`` list. As you can see, the append function for -a ``QQmlListProperty`` is called with two arguments: the list property, and the +a ``ListProperty`` is called with two arguments: the list property, and the item that is to be appended. The ``PieSlice`` class has also been modified to include ``fromAngle`` and diff --git a/examples/quickcontrols/contactslist/main.py b/examples/quickcontrols/contactslist/main.py index 6c934ec8..d501bbdb 100644 --- a/examples/quickcontrols/contactslist/main.py +++ b/examples/quickcontrols/contactslist/main.py @@ -23,5 +23,6 @@ if __name__ == '__main__': if not engine.rootObjects(): sys.exit(-1) + ex = app.exec() del engine - sys.exit(app.exec()) + sys.exit(ex) diff --git a/examples/quickcontrols/filesystemexplorer/FileSystemModule/Main.qml b/examples/quickcontrols/filesystemexplorer/FileSystemModule/Main.qml index 3987acc9..7f7798ed 100644 --- a/examples/quickcontrols/filesystemexplorer/FileSystemModule/Main.qml +++ b/examples/quickcontrols/filesystemexplorer/FileSystemModule/Main.qml @@ -6,47 +6,68 @@ import QtQuick.Controls.Basic import QtQuick.Layouts import FileSystemModule +pragma ComponentBehavior: Bound + ApplicationWindow { id: root + + property bool expandPath: false + property bool showLineNumbers: true + property string currentFilePath: "" + width: 1100 height: 600 + minimumWidth: 200 + minimumHeight: 100 visible: true + color: Colors.background flags: Qt.Window | Qt.FramelessWindowHint - title: qsTr("Qt Quick Controls - File System Explorer") + title: qsTr("File System Explorer Example") - property string currentFilePath: "" - property bool expandPath: false + function getInfoText() : string { + let out = root.currentFilePath + if (!out) + return qsTr("File System Explorer") + return root.expandPath ? out : out.substring(out.lastIndexOf("/") + 1, out.length) + } menuBar: MyMenuBar { - rootWindow: root - - infoText: currentFilePath - ? (expandPath ? currentFilePath - : currentFilePath.substring(currentFilePath.lastIndexOf("/") + 1, currentFilePath.length)) - : "File System Explorer" - + dragWindow: root + infoText: root.getInfoText() MyMenu { title: qsTr("File") Action { text: qsTr("Increase Font") shortcut: StandardKey.ZoomIn - onTriggered: textArea.font.pixelSize += 1 + onTriggered: editor.text.font.pixelSize += 1 } Action { text: qsTr("Decrease Font") shortcut: StandardKey.ZoomOut - onTriggered: textArea.font.pixelSize -= 1 + onTriggered: editor.text.font.pixelSize -= 1 } Action { - text: expandPath ? qsTr("Toggle Short Path") : qsTr("Toggle Expand Path") - enabled: currentFilePath - onTriggered: expandPath = !expandPath + text: root.showLineNumbers ? qsTr("Toggle Line Numbers OFF") + : qsTr("Toggle Line Numbers ON") + shortcut: "Ctrl+L" + onTriggered: root.showLineNumbers = !root.showLineNumbers + } + Action { + text: root.expandPath ? qsTr("Toggle Short Path") + : qsTr("Toggle Expand Path") + enabled: root.currentFilePath + onTriggered: root.expandPath = !root.expandPath + } + Action { + text: qsTr("Reset Filesystem") + enabled: sidebar.currentTabIndex === 1 + onTriggered: fileSystemView.rootIndex = undefined } Action { text: qsTr("Exit") onTriggered: Qt.exit(0) - shortcut: "Ctrl+Q" + shortcut: StandardKey.Quit } } @@ -56,134 +77,109 @@ ApplicationWindow { Action { text: qsTr("Cut") shortcut: StandardKey.Cut - enabled: textArea.selectedText.length > 0 - onTriggered: textArea.cut() + enabled: editor.text.selectedText.length > 0 + onTriggered: editor.text.cut() } Action { text: qsTr("Copy") shortcut: StandardKey.Copy - enabled: textArea.selectedText.length > 0 - onTriggered: textArea.copy() + enabled: editor.text.selectedText.length > 0 + onTriggered: editor.text.copy() } Action { text: qsTr("Paste") shortcut: StandardKey.Paste - enabled: textArea.canPaste - onTriggered: textArea.paste() + enabled: editor.text.canPaste + onTriggered: editor.text.paste() } Action { text: qsTr("Select All") shortcut: StandardKey.SelectAll - enabled: textArea.length > 0 - onTriggered: textArea.selectAll() + enabled: editor.text.length > 0 + onTriggered: editor.text.selectAll() } Action { text: qsTr("Undo") shortcut: StandardKey.Undo - enabled: textArea.canUndo - onTriggered: textArea.undo() + enabled: editor.text.canUndo + onTriggered: editor.text.undo() } } } - - Rectangle { + // Set up the layout of the main components in a row: + // [ Sidebar, Navigation, Editor ] + RowLayout { anchors.fill: parent - color: Colors.background - - RowLayout { - anchors.fill: parent - spacing: 0 - - // Stores the buttons that navigate the application. - Sidebar { - id: sidebar - rootWindow: root - - Layout.preferredWidth: 60 - Layout.fillHeight: true - } + spacing: 0 + + // Stores the buttons that navigate the application. + Sidebar { + id: sidebar + dragWindow: root + Layout.preferredWidth: 50 + Layout.fillHeight: true + } - // Allows resizing parts of the UI. - SplitView { - Layout.fillWidth: true - Layout.fillHeight: true - - handle: Rectangle { - implicitWidth: 10 - color: SplitHandle.pressed ? Colors.color2 : Colors.background - border.color: Colors.color2 - opacity: SplitHandle.hovered || SplitHandle.pressed ? 1.0 : 0.0 - - Behavior on opacity { - OpacityAnimator { - duration: 900 - } + // Allows resizing parts of the UI. + SplitView { + Layout.fillWidth: true + Layout.fillHeight: true + // Customized handle to drag between the Navigation and the Editor. + handle: Rectangle { + implicitWidth: 10 + color: SplitHandle.pressed ? Colors.color2 : Colors.background + border.color: SplitHandle.hovered ? Colors.color2 : Colors.background + opacity: SplitHandle.hovered || navigationView.width < 15 ? 1.0 : 0.0 + + Behavior on opacity { + OpacityAnimator { + duration: 1400 } } + } - // We use an inline component to make a reusable TextArea component. - // This is convenient when the component is only used in one file. - component MyTextArea: TextArea { - antialiasing: true - color: Colors.textFile - selectedTextColor: Colors.textFile - selectionColor: Colors.selection - renderType: Text.QtRendering - textFormat: TextEdit.PlainText - - background: null - } - - Rectangle { - color: Colors.surface1 - - SplitView.preferredWidth: 250 - SplitView.fillHeight: true - - StackLayout { - currentIndex: sidebar.currentTabIndex - - anchors.fill: parent - - // Shows the help text. - MyTextArea { - readOnly: true - text: qsTr("This example shows how to use and visualize the file system.\n\n" - + "Customized Qt Quick Components have been used to achieve this look.\n\n" - + "You can edit the files but they won't be changed on the file system.\n\n" - + "Click on the folder icon to the left to get started.") - wrapMode: TextArea.Wrap - } - - // Shows the files on the file system. - FileSystemView { - id: fileSystemView - color: Colors.surface1 - - onFileClicked: (path) => root.currentFilePath = path - } + Rectangle { + id: navigationView + color: Colors.surface1 + SplitView.preferredWidth: 250 + SplitView.fillHeight: true + // The stack-layout provides different views, based on the + // selected buttons inside the sidebar. + StackLayout { + anchors.fill: parent + currentIndex: sidebar.currentTabIndex + + // Shows the help text. + Text { + text: qsTr("This example shows how to use and visualize the file system.\n\n" + + "Customized Qt Quick Components have been used to achieve this look.\n\n" + + "You can edit the files but they won't be changed on the file system.\n\n" + + "Click on the folder icon to the left to get started.") + wrapMode: TextArea.Wrap + color: Colors.text } - } - - // The ScrollView that contains the TextArea which shows the file's content. - ScrollView { - leftPadding: 20 - topPadding: 20 - bottomPadding: 20 - clip: true - - SplitView.fillWidth: true - SplitView.fillHeight: true - - property alias textArea: textArea - MyTextArea { - id: textArea - text: FileSystemModel.readFile(root.currentFilePath) + // Shows the files on the file system. + FileSystemView { + id: fileSystemView + color: Colors.surface1 + onFileClicked: path => root.currentFilePath = path } } } + + // The main view that contains the editor. + Editor { + id: editor + showLineNumbers: root.showLineNumbers + currentFilePath: root.currentFilePath + SplitView.fillWidth: true + SplitView.fillHeight: true + } } - ResizeButton {} + } + + ResizeButton { + resizeWindow: root } } diff --git a/examples/quickcontrols/filesystemexplorer/FileSystemModule/app.qrc b/examples/quickcontrols/filesystemexplorer/FileSystemModule/app.qrc index 05fc728e..fec76fe6 100644 --- a/examples/quickcontrols/filesystemexplorer/FileSystemModule/app.qrc +++ b/examples/quickcontrols/filesystemexplorer/FileSystemModule/app.qrc @@ -3,9 +3,9 @@ qmldir Main.qml qml/About.qml + qml/Editor.qml qml/Colors.qml qml/FileSystemView.qml - qml/Icon.qml qml/MyMenu.qml qml/MyMenuBar.qml qml/ResizeButton.qml diff --git a/examples/quickcontrols/filesystemexplorer/FileSystemModule/icons.qrc b/examples/quickcontrols/filesystemexplorer/FileSystemModule/icons.qrc index 5793a62c..97d8a3d7 100644 --- a/examples/quickcontrols/filesystemexplorer/FileSystemModule/icons.qrc +++ b/examples/quickcontrols/filesystemexplorer/FileSystemModule/icons.qrc @@ -1,5 +1,6 @@ + icons/app_icon.svg icons/folder_closed.svg icons/folder_open.svg icons/generic_file.svg diff --git a/examples/quickcontrols/filesystemexplorer/FileSystemModule/icons/app_icon.svg b/examples/quickcontrols/filesystemexplorer/FileSystemModule/icons/app_icon.svg new file mode 100644 index 00000000..5aae4221 --- /dev/null +++ b/examples/quickcontrols/filesystemexplorer/FileSystemModule/icons/app_icon.svg @@ -0,0 +1,2 @@ + + diff --git a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/About.qml b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/About.qml index b7bc0ac6..178bf03e 100644 --- a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/About.qml +++ b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/About.qml @@ -7,53 +7,87 @@ import FileSystemModule ApplicationWindow { id: root - width: 500 - height: 360 + width: 650 + height: 550 flags: Qt.Window | Qt.FramelessWindowHint color: Colors.surface1 menuBar: MyMenuBar { id: menuBar - implicitHeight: 20 - rootWindow: root + + dragWindow: root + implicitHeight: 27 infoText: "About Qt" } Image { id: logo + anchors.left: parent.left anchors.right: parent.right anchors.top: parent.top anchors.margins: 20 + source: "../icons/qt_logo.svg" - sourceSize: Qt.size(80, 80) + sourceSize.width: 80 + sourceSize.height: 80 fillMode: Image.PreserveAspectFit + smooth: true antialiasing: true asynchronous: true } - TextArea { - anchors.top: logo.bottom - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: parent.bottom - anchors.margins: 20 - antialiasing: true - wrapMode: Text.WrapAnywhere - color: Colors.textFile - horizontalAlignment: Text.AlignHCenter - readOnly: true - selectionColor: Colors.selection - text: qsTr("Qt Group (Nasdaq Helsinki: QTCOM) is a global software company with a strong \ -presence in more than 70 industries and is the leading independent technology behind 1+ billion \ -devices and applications. Qt is used by major global companies and developers worldwide, and the \ -technology enables its customers to deliver exceptional user experiences and advance their digital \ -transformation initiatives. Qt achieves this through its cross-platform software framework for the \ -development of apps and devices, under both commercial and open-source licenses.") - background: Rectangle { - color: "transparent" - } + ScrollView { + anchors.top: logo.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.margins: 20 + + TextArea { + selectedTextColor: Colors.textFile + selectionColor: Colors.selection + horizontalAlignment: Text.AlignHCenter + textFormat: Text.RichText + + text: qsTr("

About Qt

" + + "

This program uses Qt version %1.

" + + "

Qt is a C++ toolkit for cross-platform application " + + "development.

" + + "

Qt provides single-source portability across all major desktop " + + "operating systems. It is also available for embedded Linux and other " + + "embedded and mobile operating systems.

" + + "

Qt is available under multiple licensing options designed " + + "to accommodate the needs of our various users.

" + + "

Qt licensed under our commercial license agreement is appropriate " + + "for development of proprietary/commercial software where you do not " + + "want to share any source code with third parties or otherwise cannot " + + "comply with the terms of GNU (L)GPL.

" + + "

Qt licensed under GNU (L)GPL is appropriate for the " + + "development of Qt applications provided you can comply with the terms " + + "and conditions of the respective licenses.

" + + "

Please see %2 " + + "for an overview of Qt licensing.

" + + "

Copyright (C) %3 The Qt Company Ltd and other " + + "contributors.

" + + "

Qt and the Qt logo are trademarks of The Qt Company Ltd.

" + + "

Qt is The Qt Company Ltd product developed as an open source " + + "project. See %4 for more information.

") + .arg(Application.version).arg("qt.io/licensing").arg("2023").arg("qt.io") + color: Colors.textFile + wrapMode: Text.WordWrap + readOnly: true + antialiasing: true + background: null + + onLinkActivated: function(link) { + Qt.openUrlExternally(link) + } + } + } + + ResizeButton { + resizeWindow: root } - ResizeButton {} } diff --git a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/Colors.qml b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/Colors.qml index 280f8928..28566777 100644 --- a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/Colors.qml +++ b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/Colors.qml @@ -1,22 +1,23 @@ // Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause -pragma Singleton import QtQuick +pragma Singleton + QtObject { - readonly property color background: "#23272E" - readonly property color surface1: "#1E2227" + readonly property color background: "#292828" + readonly property color surface1: "#171819" readonly property color surface2: "#090A0C" - readonly property color text: "#ABB2BF" - readonly property color textFile: "#C5CAD3" - readonly property color disabledText: "#454D5F" - readonly property color selection: "#2C313A" - readonly property color active: "#23272E" - readonly property color inactive: "#3E4452" - readonly property color folder: "#3D4451" - readonly property color icon: "#3D4451" - readonly property color iconIndicator: "#E5C07B" - readonly property color color1: "#E06B74" - readonly property color color2: "#62AEEF" + readonly property color text: "#D4BE98" + readonly property color textFile: "#E1D2B7" + readonly property color disabledText: "#2C313A" + readonly property color selection: "#4B4A4A" + readonly property color active: "#292828" + readonly property color inactive: "#383737" + readonly property color folder: "#383737" + readonly property color icon: "#383737" + readonly property color iconIndicator: "#D5B35D" + readonly property color color1: "#A7B464" + readonly property color color2: "#D3869B" } diff --git a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/Editor.qml b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/Editor.qml new file mode 100644 index 00000000..80f7c04c --- /dev/null +++ b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/Editor.qml @@ -0,0 +1,160 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import FileSystemModule + +pragma ComponentBehavior: Bound + +// This is the text editor that displays the currently open file, including +// their corresponding line numbers. +Rectangle { + id: root + + required property string currentFilePath + required property bool showLineNumbers + property alias text: textArea + property int currentLineNumber: -1 + property int rowHeight: Math.ceil(fontMetrics.lineSpacing) + + color: Colors.background + + onWidthChanged: textArea.update() + onHeightChanged: textArea.update() + + RowLayout { + anchors.fill: parent + // We use a flickable to synchronize the position of the editor and + // the line numbers. This is necessary because the line numbers can + // extend the available height. + Flickable { + id: lineNumbers + + // Calculate the width based on the logarithmic scale. + Layout.preferredWidth: fontMetrics.averageCharacterWidth + * (Math.floor(Math.log10(textArea.lineCount)) + 1) + 10 + Layout.fillHeight: true + + interactive: false + contentY: editorFlickable.contentY + visible: textArea.text !== "" && root.showLineNumbers + + Column { + anchors.fill: parent + Repeater { + id: repeatedLineNumbers + + model: LineNumberModel { + lineCount: textArea.text !== "" ? textArea.lineCount : 0 + } + + delegate: Item { + required property int index + + width: parent.width + height: root.rowHeight + Label { + id: numbers + + text: parent.index + 1 + + width: parent.width + height: parent.height + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + + color: (root.currentLineNumber === parent.index) + ? Colors.iconIndicator : Qt.darker(Colors.text, 2) + font: textArea.font + } + Rectangle { + id: indicator + + anchors.left: numbers.right + width: 1 + height: parent.height + color: Qt.darker(Colors.text, 3) + } + } + } + } + } + + Flickable { + id: editorFlickable + + property alias textArea: textArea + + // We use an inline component to customize the horizontal and vertical + // scroll-bars. This is convenient when the component is only used in one file. + component MyScrollBar: ScrollBar { + id: scrollBar + background: Rectangle { + implicitWidth: scrollBar.interactive ? 8 : 4 + implicitHeight: scrollBar.interactive ? 8 : 4 + + opacity: scrollBar.active && scrollBar.size < 1.0 ? 1.0 : 0.0 + color: Colors.background + Behavior on opacity { + OpacityAnimator { + duration: 500 + } + } + } + contentItem: Rectangle { + implicitWidth: scrollBar.interactive ? 8 : 4 + implicitHeight: scrollBar.interactive ? 8 : 4 + opacity: scrollBar.active && scrollBar.size < 1.0 ? 1.0 : 0.0 + color: Colors.color1 + Behavior on opacity { + OpacityAnimator { + duration: 1000 + } + } + } + } + + Layout.fillHeight: true + Layout.fillWidth: true + ScrollBar.horizontal: MyScrollBar {} + ScrollBar.vertical: MyScrollBar {} + + boundsBehavior: Flickable.StopAtBounds + + TextArea.flickable: TextArea { + id: textArea + anchors.fill: parent + + focus: false + topPadding: 0 + leftPadding: 10 + + text: FileSystemModel.readFile(root.currentFilePath) + tabStopDistance: fontMetrics.averageCharacterWidth * 4 + + // Grab the current line number from the C++ interface. + onCursorPositionChanged: { + root.currentLineNumber = FileSystemModel.currentLineNumber( + textArea.textDocument, textArea.cursorPosition) + } + + color: Colors.textFile + selectedTextColor: Colors.textFile + selectionColor: Colors.selection + + textFormat: TextEdit.PlainText + renderType: Text.QtRendering + selectByMouse: true + antialiasing: true + background: null + } + + FontMetrics { + id: fontMetrics + font: textArea.font + } + } + } +} diff --git a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/FileSystemView.qml b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/FileSystemView.qml index ade2e48c..db955168 100644 --- a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/FileSystemView.qml +++ b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/FileSystemView.qml @@ -2,26 +2,31 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause import QtQuick -import QtQuick.Layouts +import QtQuick.Effects import QtQuick.Controls.Basic import FileSystemModule +pragma ComponentBehavior: Bound + // This is the file system view which gets populated by the C++ model. Rectangle { id: root signal fileClicked(string filePath) + property alias rootIndex: fileSystemTreeView.rootIndex TreeView { id: fileSystemTreeView + + property int lastIndex: -1 + anchors.fill: parent model: FileSystemModel + rootIndex: FileSystemModel.rootIndex boundsBehavior: Flickable.StopAtBounds boundsMovement: Flickable.StopAtBounds clip: true - property int lastIndex: -1 - Component.onCompleted: fileSystemTreeView.toggleExpanded(0) // The delegate represents a single entry in the filesystem. @@ -31,50 +36,101 @@ Rectangle { implicitWidth: fileSystemTreeView.width > 0 ? fileSystemTreeView.width : 250 implicitHeight: 25 + // Since we have the 'ComponentBehavior Bound' pragma, we need to + // require these properties from our model. This is a convenient way + // to bind the properties provided by the model's role names. required property int index required property url filePath + required property string fileName - indicator: null - - contentItem: Item { - anchors.fill: parent + indicator: Image { + id: directoryIcon - Icon { - id: directoryIcon - x: leftMargin + (depth * indentation) - anchors.verticalCenter: parent.verticalCenter - path: treeDelegate.hasChildren - ? (treeDelegate.expanded ? "../icons/folder_open.svg" : "../icons/folder_closed.svg") + x: treeDelegate.leftMargin + (treeDelegate.depth * treeDelegate.indentation) + anchors.verticalCenter: parent.verticalCenter + source: treeDelegate.hasChildren ? (treeDelegate.expanded + ? "../icons/folder_open.svg" : "../icons/folder_closed.svg") : "../icons/generic_file.svg" - iconColor: (treeDelegate.expanded && treeDelegate.hasChildren) ? Colors.color2 : Colors.folder - } - Text { - anchors.left: directoryIcon.right - anchors.verticalCenter: parent.verticalCenter - width: parent.width - text: model.fileName - color: Colors.text - } + sourceSize.width: 20 + sourceSize.height: 20 + fillMode: Image.PreserveAspectFit + + smooth: true + antialiasing: true + asynchronous: true + } + + contentItem: Text { + text: treeDelegate.fileName + color: Colors.text } background: Rectangle { - color: treeDelegate.index === fileSystemTreeView.lastIndex + color: (treeDelegate.index === fileSystemTreeView.lastIndex) ? Colors.selection : (hoverHandler.hovered ? Colors.active : "transparent") } - TapHandler { - onSingleTapped: { - fileSystemTreeView.toggleExpanded(row) - fileSystemTreeView.lastIndex = index - // If this model item doesn't have children, it means it's representing a file. - if (!treeDelegate.hasChildren) - root.fileClicked(filePath) + // We color the directory icons with this MultiEffect, where we overlay + // the colorization color ontop of the SVG icons. + MultiEffect { + id: iconOverlay + + anchors.fill: directoryIcon + source: directoryIcon + colorization: 1.0 + brightness: 1.0 + colorizationColor: { + const isFile = treeDelegate.index === fileSystemTreeView.lastIndex + && !treeDelegate.hasChildren; + if (isFile) + return Qt.lighter(Colors.folder, 3) + + const isExpandedFolder = treeDelegate.expanded && treeDelegate.hasChildren; + if (isExpandedFolder) + return Colors.color2 + else + return Colors.folder } } + HoverHandler { id: hoverHandler } + + TapHandler { + acceptedButtons: Qt.LeftButton | Qt.RightButton + onSingleTapped: (eventPoint, button) => { + switch (button) { + case Qt.LeftButton: + fileSystemTreeView.toggleExpanded(treeDelegate.row) + fileSystemTreeView.lastIndex = treeDelegate.index + // If this model item doesn't have children, it means it's + // representing a file. + if (!treeDelegate.hasChildren) + root.fileClicked(treeDelegate.filePath) + break; + case Qt.RightButton: + if (treeDelegate.hasChildren) + contextMenu.popup(); + break; + } + } + } + + MyMenu { + id: contextMenu + Action { + text: qsTr("Set as root index") + onTriggered: { + fileSystemTreeView.rootIndex = fileSystemTreeView.index(treeDelegate.row, 0) + } + } + Action { + text: qsTr("Reset root index") + onTriggered: fileSystemTreeView.rootIndex = undefined + } + } } // Provide our own custom ScrollIndicator for the TreeView. @@ -85,6 +141,7 @@ Rectangle { contentItem: Rectangle { implicitWidth: 6 implicitHeight: 6 + color: Colors.color1 opacity: fileSystemTreeView.movingVertically ? 0.5 : 0.0 diff --git a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/Icon.qml b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/Icon.qml deleted file mode 100644 index 25162d9d..00000000 --- a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/Icon.qml +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (C) 2023 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -import QtQuick -import QtQuick.Effects - -// Custom Component for displaying Icons -Item { - id: root - - required property url path - property real padding: 5 - property real size: 30 - property alias iconColor: overlay.colorizationColor - property alias hovered: mouse.hovered - - width: size - height: size - - Image { - id: icon - anchors.fill: root - anchors.margins: padding - source: path - sourceSize: Qt.size(size, size) - fillMode: Image.PreserveAspectFit - smooth: true - antialiasing: true - asynchronous: true - } - - MultiEffect { - id: overlay - anchors.fill: icon - source: icon - colorization: 1.0 - brightness: 1.0 - } - - HoverHandler { - id: mouse - acceptedDevices: PointerDevice.Mouse - } -} diff --git a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/MyMenu.qml b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/MyMenu.qml index 99795b5e..1f1d30c5 100644 --- a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/MyMenu.qml +++ b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/MyMenu.qml @@ -8,35 +8,38 @@ import FileSystemModule Menu { id: root - background: Rectangle { - implicitWidth: 200 - implicitHeight: 40 - color: Colors.surface2 - } - delegate: MenuItem { id: menuItem - implicitWidth: 200 - implicitHeight: 40 contentItem: Item { Text { anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left anchors.leftMargin: 5 + text: menuItem.text color: enabled ? Colors.text : Colors.disabledText } Rectangle { + id: indicator + anchors.verticalCenter: parent.verticalCenter anchors.right: parent.right width: 6 height: parent.height + visible: menuItem.highlighted color: Colors.color2 } } background: Rectangle { + implicitWidth: 210 + implicitHeight: 35 color: menuItem.highlighted ? Colors.active : "transparent" } } + background: Rectangle { + implicitWidth: 210 + implicitHeight: 35 + color: Colors.surface2 + } } diff --git a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/MyMenuBar.qml b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/MyMenuBar.qml index a2a3fea8..4874a2c0 100644 --- a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/MyMenuBar.qml +++ b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/MyMenuBar.qml @@ -6,130 +6,172 @@ import QtQuick.Layouts import QtQuick.Controls.Basic import FileSystemModule -// The MenuBar also serves as a controller for our Window as we don't use any decorations. +// The MenuBar also serves as a controller for our window as we don't use any decorations. MenuBar { id: root - required property ApplicationWindow rootWindow + required property ApplicationWindow dragWindow property alias infoText: windowInfo.text - implicitHeight: 25 - - // The top level menus on the left side + // Customization of the top level menus inside the MenuBar delegate: MenuBarItem { id: menuBarItem - implicitHeight: 25 contentItem: Text { horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter - color: menuBarItem.highlighted ? Colors.textFile : Colors.text - opacity: enabled ? 1.0 : 0.3 + text: menuBarItem.text - elide: Text.ElideRight font: menuBarItem.font + elide: Text.ElideRight + color: menuBarItem.highlighted ? Colors.textFile : Colors.text + opacity: enabled ? 1.0 : 0.3 } background: Rectangle { + id: background + color: menuBarItem.highlighted ? Colors.selection : "transparent" Rectangle { id: indicator + width: 0; height: 3 anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: parent.bottom - color: Colors.color1 + color: Colors.color1 states: State { - name: "active"; when: menuBarItem.highlighted - PropertyChanges { target: indicator; width: parent.width } + name: "active" + when: menuBarItem.highlighted + PropertyChanges { + indicator.width: background.width - 2 + } } - transitions: Transition { NumberAnimation { properties: "width" - duration: 300 + duration: 175 } } - } } } + // We use the contentItem property as a place to attach our window decorations. Beneath + // the usual menu entries within a MenuBar, it includes a centered information text, along + // with the minimize, maximize, and close buttons. + contentItem: RowLayout { + id: windowBar - // The background property contains an information text in the middle as well as the - // Minimize, Maximize and Close Buttons. - background: Rectangle { - color: Colors.surface2 - // Make the empty space drag the specified root window. - WindowDragHandler { dragWindow: rootWindow } + Layout.fillWidth: true + Layout.fillHeight: true + + spacing: root.spacing + Repeater { + id: menuBarItems - Text { - id: windowInfo - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - color: Colors.text + Layout.alignment: Qt.AlignLeft + model: root.contentModel } - component InteractionButton: Rectangle { - signal action; - property alias hovered: hoverHandler.hovered + Item { + Layout.fillWidth: true + Layout.fillHeight: true + Text { + id: windowInfo + + width: parent.width; height: parent.height + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + leftPadding: windowActions.width + color: Colors.text + clip: true + } + } - width: root.height - anchors.top: parent.top - anchors.bottom: parent.bottom - color: hovered ? Colors.background : "transparent" + RowLayout { + id: windowActions - HoverHandler { id: hoverHandler } - TapHandler { onTapped: action() } - } + Layout.alignment: Qt.AlignRight + Layout.fillHeight: true - InteractionButton { - id: minimize + spacing: 0 - anchors.right: maximize.left - onAction: rootWindow.showMinimized() - Rectangle { - width: parent.height - 10; height: 2 - anchors.centerIn: parent - color: parent.hovered ? Colors.iconIndicator : Colors.icon + component InteractionButton: Rectangle { + id: interactionButton + + signal action() + property alias hovered: hoverHandler.hovered + + Layout.fillHeight: true + Layout.preferredWidth: height + + color: hovered ? Colors.background : "transparent" + HoverHandler { + id: hoverHandler + } + TapHandler { + id: tapHandler + onTapped: interactionButton.action() + } } - } - InteractionButton { - id: maximize + InteractionButton { + id: minimize - anchors.right: close.left - onAction: rootWindow.showMaximized() - Rectangle { - anchors.fill: parent - anchors.margins: 5 - border.width: 2 - color: "transparent" - border.color: parent.hovered ? Colors.iconIndicator : Colors.icon + onAction: root.dragWindow.showMinimized() + Rectangle { + anchors.centerIn: parent + color: parent.hovered ? Colors.iconIndicator : Colors.icon + height: 2 + width: parent.height - 14 + } } - } - InteractionButton { - id: close + InteractionButton { + id: maximize - color: hovered ? "#ec4143" : "transparent" - anchors.right: parent.right - onAction: rootWindow.close() - Rectangle { - width: parent.height - 8; height: 2 - anchors.centerIn: parent - color: parent.hovered ? Colors.iconIndicator : Colors.icon - rotation: 45 - transformOrigin: Item.Center - antialiasing: true + onAction: root.dragWindow.showMaximized() + Rectangle { + anchors.fill: parent + anchors.margins: 7 + border.color: parent.hovered ? Colors.iconIndicator : Colors.icon + border.width: 2 + color: "transparent" + } + } + + InteractionButton { + id: close + + color: hovered ? "#ec4143" : "transparent" + onAction: root.dragWindow.close() Rectangle { - width: parent.height - height: parent.width anchors.centerIn: parent - color: parent.color + width: parent.height - 8; height: 2 + + rotation: 45 antialiasing: true + transformOrigin: Item.Center + color: parent.hovered ? Colors.iconIndicator : Colors.icon + + Rectangle { + anchors.centerIn: parent + width: parent.height + height: parent.width + + antialiasing: true + color: parent.color + } } } } } + background: Rectangle { + color: Colors.surface2 + // Make the empty space drag the specified root window. + WindowDragHandler { + dragWindow: root.dragWindow + } + } } diff --git a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/ResizeButton.qml b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/ResizeButton.qml index eb2e5bc0..0df65bf8 100644 --- a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/ResizeButton.qml +++ b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/ResizeButton.qml @@ -5,6 +5,8 @@ import QtQuick.Controls import FileSystemModule Button { + required property ApplicationWindow resizeWindow + icon.width: 20; icon.height: 20 anchors.right: parent.right anchors.bottom: parent.bottom @@ -12,12 +14,10 @@ Button { bottomPadding: 3 icon.source: "../icons/resize.svg" - icon.color: down || checked ? Colors.iconIndicator : Colors.icon + icon.color: hovered ? Colors.iconIndicator : Colors.icon + background: null checkable: false display: AbstractButton.IconOnly - background: null - onPressed: { - root.startSystemResize(Qt.BottomEdge | Qt.RightEdge) - } + onPressed: resizeWindow.startSystemResize(Qt.BottomEdge | Qt.RightEdge) } diff --git a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/Sidebar.qml b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/Sidebar.qml index 9d08562d..aac53039 100644 --- a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/Sidebar.qml +++ b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/Sidebar.qml @@ -8,77 +8,92 @@ import FileSystemModule Rectangle { id: root + + property alias currentTabIndex: topBar.currentIndex + required property ApplicationWindow dragWindow + readonly property int tabBarSpacing: 10 + color: Colors.surface2 - required property ApplicationWindow rootWindow - property alias currentTabIndex: tabBar.currentIndex + component SidebarEntry: Button { + id: sidebarButton - ColumnLayout { - anchors.fill: root - anchors.topMargin: 10 - anchors.bottomMargin: 10 - spacing: 10 + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true - // TabBar is designed to be horizontal, whereas we need a vertical bar. - // We can easily achieve that by using a Container. - Container { - id: tabBar + icon.color: down || checked ? Colors.iconIndicator : Colors.icon + icon.width: 27 + icon.height: 27 - Layout.fillWidth: true + topPadding: 0 + rightPadding: 0 + bottomPadding: 0 + leftPadding: 0 + background: null - // ButtonGroup ensures that only one button can be checked at a time. - ButtonGroup { - buttons: tabBar.contentItem.children - // We have to manage the currentIndex ourselves, which we do by setting it to the - // index of the currently checked button. - // We use setCurrentIndex instead of setting the currentIndex property to avoid breaking bindings. - // See "Managing the Current Index" in Container's documentation for more information. - onCheckedButtonChanged: tabBar.setCurrentIndex(Math.max(0, buttons.indexOf(checkedButton))) - } + Rectangle { + id: indicator - contentItem: ColumnLayout { - spacing: tabBar.spacing + anchors.verticalCenter: parent.verticalCenter + x: 2 + width: 4 + height: sidebarButton.icon.height * 1.2 - Repeater { - model: tabBar.contentModel - } - } + visible: sidebarButton.checked + color: Colors.color1 + } + } + + // TabBar is designed to be horizontal, whereas we need a vertical bar. + // We can easily achieve that by using a Container. + component TabBar: Container { + id: tabBarComponent + + Layout.fillWidth: true + // ButtonGroup ensures that only one button can be checked at a time. + ButtonGroup { + buttons: tabBarComponent.contentChildren + + // We have to manage the currentIndex ourselves, which we do by setting it to the index + // of the currently checked button. We use setCurrentIndex instead of setting the + // currentIndex property to avoid breaking bindings. See "Managing the Current Index" + // in Container's documentation for more information. + onCheckedButtonChanged: tabBarComponent.setCurrentIndex( + Math.max(0, buttons.indexOf(checkedButton))) + } - component SidebarEntry: Button { - id: sidebarButton - icon.color: down || checked ? Colors.iconIndicator : Colors.icon - icon.width: 35 - icon.height: 35 - leftPadding: 8 + indicator.width - - background: null - - Rectangle { - id: indicator - x: 4 - anchors.verticalCenter: parent.verticalCenter - width: 4 - height: sidebarButton.icon.width - color: Colors.color1 - visible: sidebarButton.checked - } + contentItem: ColumnLayout { + spacing: tabBarComponent.spacing + Repeater { + model: tabBarComponent.contentModel } + } + } + ColumnLayout { + anchors.fill: root + anchors.topMargin: root.tabBarSpacing + anchors.bottomMargin: root.tabBarSpacing + + spacing: root.tabBarSpacing + TabBar { + id: topBar + + spacing: root.tabBarSpacing // Shows help text when clicked. SidebarEntry { + id: infoTab icon.source: "../icons/light_bulb.svg" checkable: true checked: true - - Layout.alignment: Qt.AlignHCenter } // Shows the file system when clicked. SidebarEntry { + id: filesystemTab + icon.source: "../icons/read.svg" checkable: true - - Layout.alignment: Qt.AlignHCenter } } @@ -88,25 +103,31 @@ Rectangle { Layout.fillWidth: true // Make the empty space drag our main window. - WindowDragHandler { dragWindow: rootWindow } + WindowDragHandler { + dragWindow: root.dragWindow + } } - // Opens the Qt website in the system's web browser. - SidebarEntry { - id: qtWebsiteButton - icon.source: "../icons/globe.svg" - checkable: false + TabBar { + id: bottomBar - onClicked: Qt.openUrlExternally("https://www.qt.io/") - } + spacing: root.tabBarSpacing + // Opens the Qt website in the system's web browser. + SidebarEntry { + id: qtWebsiteButton + icon.source: "../icons/globe.svg" + checkable: false + onClicked: Qt.openUrlExternally("https://www.qt.io/") + } - // Opens the About Qt Window. - SidebarEntry { - id: aboutQtButton - icon.source: "../icons/info_sign.svg" - checkable: false + // Opens the About Qt Window. + SidebarEntry { + id: aboutQtButton - onClicked: aboutQtWindow.visible = !aboutQtWindow.visible + icon.source: "../icons/info_sign.svg" + checkable: false + onClicked: aboutQtWindow.visible = !aboutQtWindow.visible + } } } diff --git a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qmldir b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qmldir index ff7ecb75..b1f68460 100644 --- a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qmldir +++ b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qmldir @@ -1,7 +1,7 @@ module FileSystemModule Main 1.0 Main.qml -Icon 1.0 qml/Icon.qml About 1.0 qml/About.qml +Editor 1.0 qml/Editor.qml MyMenu 1.0 qml/MyMenu.qml Sidebar 1.0 qml/Sidebar.qml MyMenuBar 1.0 qml/MyMenuBar.qml diff --git a/examples/quickcontrols/filesystemexplorer/doc/filesystemexplorer.rst b/examples/quickcontrols/filesystemexplorer/doc/filesystemexplorer.rst index 0260928b..b46cbec7 100644 --- a/examples/quickcontrols/filesystemexplorer/doc/filesystemexplorer.rst +++ b/examples/quickcontrols/filesystemexplorer/doc/filesystemexplorer.rst @@ -37,3 +37,16 @@ pleasing UIs. .. image:: filesystemexplorer.webp :target: filesystemexplorer.webp :alt: QtQuickControls Filesystem Explorer Screenshot + +References +---------- + +If you're interested in the C++ version of this example, you can find it +`here `_. + +Additionally, there is a detailed +`tutorial `_ +available that provides step-by-step instructions on how to extend this example +with additional features. This tutorial can be helpful if you want to explore +and learn more about building upon the existing functionality of the filesystem +explorer. diff --git a/examples/quickcontrols/filesystemexplorer/doc/filesystemexplorer.webp b/examples/quickcontrols/filesystemexplorer/doc/filesystemexplorer.webp index cce7e1da..10ad0d26 100644 Binary files a/examples/quickcontrols/filesystemexplorer/doc/filesystemexplorer.webp and b/examples/quickcontrols/filesystemexplorer/doc/filesystemexplorer.webp differ diff --git a/examples/quickcontrols/filesystemexplorer/editormodels.py b/examples/quickcontrols/filesystemexplorer/editormodels.py new file mode 100644 index 00000000..68814772 --- /dev/null +++ b/examples/quickcontrols/filesystemexplorer/editormodels.py @@ -0,0 +1,116 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from PySide6.QtWidgets import QFileSystemModel +from PySide6.QtQuick import QQuickTextDocument +from PySide6.QtQml import QmlElement, QmlSingleton +from PySide6.QtCore import (Qt, QDir, QAbstractListModel, Slot, QFile, QTextStream, + QMimeDatabase, QFileInfo, QStandardPaths, QModelIndex, + Signal, Property) + +QML_IMPORT_NAME = "FileSystemModule" +QML_IMPORT_MAJOR_VERSION = 1 + + +@QmlElement +@QmlSingleton +class FileSystemModel(QFileSystemModel): + + rootIndexChanged = Signal() + + def getDefaultRootDir(): + return QStandardPaths.writableLocation(QStandardPaths.StandardLocation.HomeLocation) + + def __init__(self, parent=None): + super().__init__(parent=parent) + self.mRootIndex = QModelIndex() + self.mDb = QMimeDatabase() + self.setFilter(QDir.Filter.AllEntries | QDir.Filter.Hidden | QDir.Filter.NoDotAndDotDot) + self.setInitialDirectory() + + # check for the correct mime type and then read the file. + # returns the text file's content or an error message on failure + @Slot(str, result=str) + def readFile(self, path): + if path == "": + return "" + + file = QFile(path) + + mime = self.mDb.mimeTypeForFile(QFileInfo(file)) + if ('text' in mime.comment().lower() + or any('text' in s.lower() for s in mime.parentMimeTypes())): + if file.open(QFile.OpenModeFlag.ReadOnly | QFile.OpenModeFlag.Text): + stream = QTextStream(file).readAll() + file.close() + return stream + else: + return self.tr("Error opening the file!") + return self.tr("File type not supported!") + + @Slot(QQuickTextDocument, int, result=int) + def currentLineNumber(self, textDocument, cursorPosition): + td = textDocument.textDocument() + tb = td.findBlock(cursorPosition) + return tb.blockNumber() + + def setInitialDirectory(self, path=getDefaultRootDir()): + dir = QDir(path) + if dir.makeAbsolute(): + self.setRootPath(dir.path()) + else: + self.setRootPath(self.getDefaultRootDir()) + self.setRootIndex(self.index(dir.path())) + + # we only need one column in this example + def columnCount(self, parent): + return 1 + + @Property(QModelIndex, notify=rootIndexChanged) + def rootIndex(self): + return self.mRootIndex + + def setRootIndex(self, index): + if (index == self.mRootIndex): + return + self.mRootIndex = index + self.rootIndexChanged.emit() + + +@QmlElement +class LineNumberModel(QAbstractListModel): + + lineCountChanged = Signal() + + def __init__(self, parent=None): + self.mLineCount = 0 + super().__init__(parent=parent) + + @Property(int, notify=lineCountChanged) + def lineCount(self): + return self.mLineCount + + @lineCount.setter + def lineCount(self, n): + if n < 0: + print("lineCount must be greater then zero") + return + if self.mLineCount == n: + return + + if self.mLineCount < n: + self.beginInsertRows(QModelIndex(), self.mLineCount, n - 1) + self.mLineCount = n + self.endInsertRows() + else: + self.beginRemoveRows(QModelIndex(), n, self.mLineCount - 1) + self.mLineCount = n + self.endRemoveRows() + + def rowCount(self, parent): + return self.mLineCount + + def data(self, index, role): + if not self.checkIndex(index) or role != Qt.ItemDataRole.DisplayRole: + return + return index.row() diff --git a/examples/quickcontrols/filesystemexplorer/filesystemexplorer.py b/examples/quickcontrols/filesystemexplorer/filesystemexplorer.py deleted file mode 100644 index 2bc733c3..00000000 --- a/examples/quickcontrols/filesystemexplorer/filesystemexplorer.py +++ /dev/null @@ -1,70 +0,0 @@ -# Copyright (C) 2023 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -""" -This example shows how to customize Qt Quick Controls by implementing a simple filesystem explorer. -""" - -# Compile both resource files app.qrc and icons.qrc and include them here if you wish -# to load them from the resource system. Currently, all resources are loaded locally -# import FileSystemModule.rc_icons -# import FileSystemModule.rc_app - -from PySide6.QtWidgets import QFileSystemModel -from PySide6.QtGui import QGuiApplication -from PySide6.QtQml import (QQmlApplicationEngine, QmlElement, QmlSingleton) -from PySide6.QtCore import (Slot, QFile, QTextStream, QMimeDatabase, QFileInfo, QStandardPaths) - -import sys - - -QML_IMPORT_NAME = "FileSystemModule" -QML_IMPORT_MAJOR_VERSION = 1 - - -@QmlElement -@QmlSingleton -class FileSystemModel(QFileSystemModel): - def __init__(self, parent=None): - super().__init__(parent=parent) - self.setRootPath(QStandardPaths.writableLocation(QStandardPaths.HomeLocation)) - self.db = QMimeDatabase() - - # we only need one column in this example - def columnCount(self, parent): - return 1 - - # check for the correct mime type and then read the file. - # returns the text file's content or an error message on failure - @Slot(str, result=str) - def readFile(self, path): - if path == "": - return "" - - file = QFile(path) - - mime = self.db.mimeTypeForFile(QFileInfo(file)) - if ('text' in mime.comment().lower() - or any('text' in s.lower() for s in mime.parentMimeTypes())): - if file.open(QFile.ReadOnly | QFile.Text): - stream = QTextStream(file).readAll() - return stream - else: - return self.tr("Error opening the file!") - return self.tr("File type not supported!") - - -if __name__ == '__main__': - app = QGuiApplication(sys.argv) - app.setOrganizationName("QtProject") - app.setApplicationName("File System Explorer") - engine = QQmlApplicationEngine() - # Include the path of this file to search for the 'qmldir' module - engine.addImportPath(sys.path[0]) - - engine.loadFromModule("FileSystemModule", "Main") - - if not engine.rootObjects(): - sys.exit(-1) - - sys.exit(app.exec()) diff --git a/examples/quickcontrols/filesystemexplorer/filesystemexplorer.pyproject b/examples/quickcontrols/filesystemexplorer/filesystemexplorer.pyproject index 1e1aa2ad..8053cfab 100644 --- a/examples/quickcontrols/filesystemexplorer/filesystemexplorer.pyproject +++ b/examples/quickcontrols/filesystemexplorer/filesystemexplorer.pyproject @@ -1,19 +1,21 @@ { "files": [ - "filesystemexplorer.py", + "main.py", + "editormodels.py", "FileSystemModule/qmldir", "FileSystemModule/app.qrc", "FileSystemModule/qmldir", "FileSystemModule/Main.qml", "FileSystemModule/qml/About.qml", "FileSystemModule/qml/Colors.qml", + "FileSystemModule/qml/Editor.qml", "FileSystemModule/qml/FileSystemView.qml", - "FileSystemModule/qml/Icon.qml", "FileSystemModule/qml/MyMenu.qml", "FileSystemModule/qml/MyMenuBar.qml", "FileSystemModule/qml/ResizeButton.qml", "FileSystemModule/qml/Sidebar.qml", "FileSystemModule/qml/WindowDragHandler.qml", + "FileSystemModule/icons/app_icon.svg", "FileSystemModule/icons/folder_closed.svg", "FileSystemModule/icons/folder_open.svg", "FileSystemModule/icons/generic_file.svg", diff --git a/examples/quickcontrols/filesystemexplorer/main.py b/examples/quickcontrols/filesystemexplorer/main.py new file mode 100644 index 00000000..8fad951c --- /dev/null +++ b/examples/quickcontrols/filesystemexplorer/main.py @@ -0,0 +1,48 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +""" +This example shows how to customize Qt Quick Controls by implementing a simple filesystem explorer. +""" + +# Compile both resource files app.qrc and icons.qrc and include them here if you wish +# to load them from the resource system. Currently, all resources are loaded locally +# import FileSystemModule.rc_icons +# import FileSystemModule.rc_app + +from editormodels import FileSystemModel # noqa: F401 +from PySide6.QtGui import QGuiApplication, QIcon +from PySide6.QtQml import QQmlApplicationEngine +from PySide6.QtCore import QCommandLineParser, qVersion + +import sys + +if __name__ == '__main__': + app = QGuiApplication(sys.argv) + app.setOrganizationName("QtProject") + app.setApplicationName("File System Explorer") + app.setApplicationVersion(qVersion()) + app.setWindowIcon(QIcon(sys.path[0] + "/FileSystemModule/icons/app_icon.svg")) + + parser = QCommandLineParser() + parser.setApplicationDescription("Qt Filesystemexplorer Example") + parser.addHelpOption() + parser.addVersionOption() + parser.addPositionalArgument("", "Initial directory", "[path]") + parser.process(app) + args = parser.positionalArguments() + + engine = QQmlApplicationEngine() + # Include the path of this file to search for the 'qmldir' module + engine.addImportPath(sys.path[0]) + + engine.loadFromModule("FileSystemModule", "Main") + + if not engine.rootObjects(): + sys.exit(-1) + + if (len(args) == 1): + fsm = engine.singletonInstance("FileSystemModule", "FileSystemModel") + fsm.setInitialDirectory(args[0]) + + sys.exit(app.exec()) diff --git a/examples/samplebinding/doc/samplebinding.rst b/examples/samplebinding/doc/samplebinding.rst index e96e99df..51b6b4c2 100644 --- a/examples/samplebinding/doc/samplebinding.rst +++ b/examples/samplebinding/doc/samplebinding.rst @@ -165,23 +165,27 @@ Using CMake You can build and run this example by executing the following commands (slightly adapted to your file system layout) in a terminal: -macOS/Linux: +Run CMake on macOS/Linux: .. code-block:: bash cd ~/pyside-setup/examples/samplebinding + mkdir build + cd build + cmake .. -B. -G Ninja -DCMAKE_BUILD_TYPE=Release -On Windows: +Run CMake on Windows: .. code-block:: bash cd C:\pyside-setup\examples\samplebinding - -.. code-block:: bash - mkdir build cd build - cmake -H.. -B. -G Ninja -DCMAKE_BUILD_TYPE=Release + cmake .. -B. -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=cl.exe + +To build: + +.. code-block:: bash ninja ninja install cd .. @@ -220,13 +224,13 @@ passing the compiler on the command line: .. code-block:: bash - cmake -H.. -B. -DCMAKE_C_COMPILER=cl.exe -DCMAKE_CXX_COMPILER=cl.exe + cmake -S.. -B. -DCMAKE_C_COMPILER=cl.exe -DCMAKE_CXX_COMPILER=cl.exe or by using the -G option: .. code-block:: bash - cmake -H.. -B. -G "Visual Studio 14 Win64" + cmake -S.. -B. -G "Visual Studio 14 Win64" If the ``-G "Visual Studio 14 Win64"`` option is used, a ``sln`` file will be generated, and can be used with ``MSBuild`` diff --git a/examples/scriptableapplication/doc/scriptableapplication.rst b/examples/scriptableapplication/doc/scriptableapplication.rst index b00d6541..bbabb124 100644 --- a/examples/scriptableapplication/doc/scriptableapplication.rst +++ b/examples/scriptableapplication/doc/scriptableapplication.rst @@ -72,21 +72,22 @@ macOS/Linux: .. code-block:: bash cd ~/pyside-setup/examples/scriptableapplication + mkdir build + cd build + cmake .. -B. -G Ninja -DCMAKE_BUILD_TYPE=Release + ninja + ./scriptableapplication On Windows: .. code-block:: bash cd C:\pyside-setup\examples\scriptableapplication - - -.. code-block:: bash - mkdir build cd build - cmake -H.. -B. -G Ninja -DCMAKE_BUILD_TYPE=Release + cmake .. -B. -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=cl.exe ninja - ./scriptableapplication + .\scriptableapplication.exe Using QMake +++++++++++ @@ -127,13 +128,13 @@ passing the compiler on the command line: .. code-block:: bash - cmake -H.. -B. -DCMAKE_C_COMPILER=cl.exe -DCMAKE_CXX_COMPILER=cl.exe + cmake -S.. -B. -DCMAKE_C_COMPILER=cl.exe -DCMAKE_CXX_COMPILER=cl.exe or using the -G option: .. code-block:: bash - cmake -H.. -B. -G "Visual Studio 14 Win64" -DCMAKE_BUILD_TYPE=Release + cmake -S.. -B. -G "Visual Studio 14 Win64" -DCMAKE_BUILD_TYPE=Release If the ``-G "Visual Studio 14 Win64"`` option is used, a ``sln`` file diff --git a/examples/scriptableapplication/mainwindow.cpp b/examples/scriptableapplication/mainwindow.cpp index 26d80a7d..ece7989e 100644 --- a/examples/scriptableapplication/mainwindow.cpp +++ b/examples/scriptableapplication/mainwindow.cpp @@ -41,18 +41,18 @@ MainWindow::MainWindow() diagnosticAction->setShortcut(Qt::CTRL | Qt::Key_D); fileMenu->addAction(tr("&Invoke testFunction1()"), this, &MainWindow::testFunction1); - const QIcon quitIcon = QIcon::fromTheme("application-exit"_L1); + const QIcon quitIcon = QIcon::fromTheme(QIcon::ThemeIcon::ApplicationExit); auto *quitAction = fileMenu->addAction(quitIcon, tr("&Quit"), qApp, &QCoreApplication::quit); quitAction->setShortcut(Qt::CTRL | Qt::Key_Q); auto *editMenu = menuBar()->addMenu(tr("&Edit")); - const QIcon clearIcon = QIcon::fromTheme("edit-clear"_L1); + const QIcon clearIcon = QIcon::fromTheme(QIcon::ThemeIcon::EditClear); auto *clearAction = editMenu->addAction(clearIcon, tr("&Clear"), m_scriptEdit, &QPlainTextEdit::clear); auto *helpMenu = menuBar()->addMenu(tr("&Help")); - const QIcon aboutIcon = QIcon::fromTheme("help-about"_L1); + const QIcon aboutIcon = QIcon::fromTheme(QIcon::ThemeIcon::HelpAbout); auto *aboutAction = helpMenu->addAction(aboutIcon, tr("&About Qt"), qApp, &QApplication::aboutQt); diff --git a/examples/scriptableapplication/mainwindow.h b/examples/scriptableapplication/mainwindow.h index 476243f9..e72f5ca7 100644 --- a/examples/scriptableapplication/mainwindow.h +++ b/examples/scriptableapplication/mainwindow.h @@ -6,7 +6,7 @@ #include -class QPlainTextEdit; +QT_FORWARD_DECLARE_CLASS(QPlainTextEdit) class MainWindow : public QMainWindow { @@ -16,6 +16,8 @@ public: void testFunction1(); + static constexpr auto TEST = QLatin1StringView("test"); + private Q_SLOTS: void slotRunScript(); void slotPrintDiagnostics(); diff --git a/examples/scriptableapplication/pythonutils.h b/examples/scriptableapplication/pythonutils.h index 7f05cded..09e95915 100644 --- a/examples/scriptableapplication/pythonutils.h +++ b/examples/scriptableapplication/pythonutils.h @@ -6,7 +6,7 @@ #include -class QObject; +QT_FORWARD_DECLARE_CLASS(QObject) namespace PythonUtils { diff --git a/examples/serialbus/can/ui_canbusdeviceinfobox.py b/examples/serialbus/can/ui_canbusdeviceinfobox.py index 250e653b..d2f0e74a 100644 --- a/examples/serialbus/can/ui_canbusdeviceinfobox.py +++ b/examples/serialbus/can/ui_canbusdeviceinfobox.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'canbusdeviceinfobox.ui' ## -## Created by: Qt User Interface Compiler version 6.4.1 +## Created by: Qt User Interface Compiler version 6.7.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ diff --git a/examples/serialbus/can/ui_canbusdeviceinfodialog.py b/examples/serialbus/can/ui_canbusdeviceinfodialog.py index fd391a2a..c32e9ca2 100644 --- a/examples/serialbus/can/ui_canbusdeviceinfodialog.py +++ b/examples/serialbus/can/ui_canbusdeviceinfodialog.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'canbusdeviceinfodialog.ui' ## -## Created by: Qt User Interface Compiler version 6.4.1 +## Created by: Qt User Interface Compiler version 6.7.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -35,7 +35,7 @@ class Ui_CanBusDeviceInfoDialog(object): self.horizontalLayout = QHBoxLayout() self.horizontalLayout.setObjectName(u"horizontalLayout") - self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) + self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum) self.horizontalLayout.addItem(self.horizontalSpacer) diff --git a/examples/serialbus/can/ui_connectdialog.py b/examples/serialbus/can/ui_connectdialog.py index 815fcf59..f01adc4e 100644 --- a/examples/serialbus/can/ui_connectdialog.py +++ b/examples/serialbus/can/ui_connectdialog.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'connectdialog.ui' ## -## Created by: Qt User Interface Compiler version 6.4.1 +## Created by: Qt User Interface Compiler version 6.7.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -169,7 +169,7 @@ class Ui_ConnectDialog(object): self.horizontalLayout = QHBoxLayout() self.horizontalLayout.setObjectName(u"horizontalLayout") - self.horizontalSpacer = QSpacerItem(96, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) + self.horizontalSpacer = QSpacerItem(96, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum) self.horizontalLayout.addItem(self.horizontalSpacer) diff --git a/examples/serialbus/can/ui_mainwindow.py b/examples/serialbus/can/ui_mainwindow.py index cce2375c..4ba4dd30 100644 --- a/examples/serialbus/can/ui_mainwindow.py +++ b/examples/serialbus/can/ui_mainwindow.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'mainwindow.ui' ## -## Created by: Qt User Interface Compiler version 6.4.1 +## Created by: Qt User Interface Compiler version 6.7.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -95,7 +95,7 @@ class Ui_MainWindow(object): self.horizontalLayout.addWidget(self.busStatus) - self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) + self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum) self.horizontalLayout.addItem(self.horizontalSpacer) @@ -119,7 +119,7 @@ class Ui_MainWindow(object): MainWindow.setMenuBar(self.menuBar) self.mainToolBar = QToolBar(MainWindow) self.mainToolBar.setObjectName(u"mainToolBar") - MainWindow.addToolBar(Qt.TopToolBarArea, self.mainToolBar) + MainWindow.addToolBar(Qt.ToolBarArea.TopToolBarArea, self.mainToolBar) self.statusBar = QStatusBar(MainWindow) self.statusBar.setObjectName(u"statusBar") MainWindow.setStatusBar(self.statusBar) diff --git a/examples/serialbus/can/ui_sendframebox.py b/examples/serialbus/can/ui_sendframebox.py index b769645e..79f013c8 100644 --- a/examples/serialbus/can/ui_sendframebox.py +++ b/examples/serialbus/can/ui_sendframebox.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'sendframebox.ui' ## -## Created by: Qt User Interface Compiler version 6.4.1 +## Created by: Qt User Interface Compiler version 6.7.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -88,7 +88,7 @@ class Ui_SendFrameBox(object): self.frameIdEdit = QLineEdit(SendFrameBox) self.frameIdEdit.setObjectName(u"frameIdEdit") - sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed) + sizePolicy = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed) sizePolicy.setHorizontalStretch(1) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.frameIdEdit.sizePolicy().hasHeightForWidth()) @@ -109,7 +109,7 @@ class Ui_SendFrameBox(object): self.payloadEdit = QLineEdit(SendFrameBox) self.payloadEdit.setObjectName(u"payloadEdit") - sizePolicy1 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) sizePolicy1.setHorizontalStretch(2) sizePolicy1.setVerticalStretch(0) sizePolicy1.setHeightForWidth(self.payloadEdit.sizePolicy().hasHeightForWidth()) diff --git a/examples/serialbus/modbus/modbusclient/ui_mainwindow.py b/examples/serialbus/modbus/modbusclient/ui_mainwindow.py index 10fb0874..1535fc8d 100644 --- a/examples/serialbus/modbus/modbusclient/ui_mainwindow.py +++ b/examples/serialbus/modbus/modbusclient/ui_mainwindow.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'mainwindow.ui' ## -## Created by: Qt User Interface Compiler version 6.4.1 +## Created by: Qt User Interface Compiler version 6.7.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -61,7 +61,7 @@ class Ui_MainWindow(object): self.gridLayout.setObjectName(u"gridLayout") self.label_27 = QLabel(self.centralWidget) self.label_27.setObjectName(u"label_27") - sizePolicy = QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Preferred) + sizePolicy = QSizePolicy(QSizePolicy.Policy.Maximum, QSizePolicy.Policy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_27.sizePolicy().hasHeightForWidth()) @@ -71,7 +71,7 @@ class Ui_MainWindow(object): self.connectButton = QPushButton(self.centralWidget) self.connectButton.setObjectName(u"connectButton") - sizePolicy1 = QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Fixed) + sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Maximum, QSizePolicy.Policy.Fixed) sizePolicy1.setHorizontalStretch(0) sizePolicy1.setVerticalStretch(0) sizePolicy1.setHeightForWidth(self.connectButton.sizePolicy().hasHeightForWidth()) @@ -81,7 +81,7 @@ class Ui_MainWindow(object): self.gridLayout.addWidget(self.connectButton, 0, 7, 1, 1) - self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) + self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum) self.gridLayout.addItem(self.horizontalSpacer, 0, 4, 1, 1) @@ -115,7 +115,7 @@ class Ui_MainWindow(object): self.portEdit = QLineEdit(self.centralWidget) self.portEdit.setObjectName(u"portEdit") - sizePolicy2 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed) + sizePolicy2 = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed) sizePolicy2.setHorizontalStretch(0) sizePolicy2.setVerticalStretch(0) sizePolicy2.setHeightForWidth(self.portEdit.sizePolicy().hasHeightForWidth()) @@ -255,7 +255,7 @@ class Ui_MainWindow(object): self.horizontalLayout.addWidget(self.writeTable) - self.horizontalSpacer_2 = QSpacerItem(13, 17, QSizePolicy.Expanding, QSizePolicy.Minimum) + self.horizontalSpacer_2 = QSpacerItem(13, 17, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum) self.horizontalLayout.addItem(self.horizontalSpacer_2) diff --git a/examples/serialbus/modbus/modbusclient/ui_settingsdialog.py b/examples/serialbus/modbus/modbusclient/ui_settingsdialog.py index f1c29959..af3a0665 100644 --- a/examples/serialbus/modbus/modbusclient/ui_settingsdialog.py +++ b/examples/serialbus/modbus/modbusclient/ui_settingsdialog.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'settingsdialog.ui' ## -## Created by: Qt User Interface Compiler version 6.4.1 +## Created by: Qt User Interface Compiler version 6.7.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -26,7 +26,7 @@ class Ui_SettingsDialog(object): SettingsDialog.resize(239, 256) self.gridLayout = QGridLayout(SettingsDialog) self.gridLayout.setObjectName(u"gridLayout") - self.verticalSpacer = QSpacerItem(20, 43, QSizePolicy.Minimum, QSizePolicy.Expanding) + self.verticalSpacer = QSpacerItem(20, 43, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding) self.gridLayout.addItem(self.verticalSpacer, 3, 1, 1, 1) diff --git a/examples/serialport/terminal/ui_mainwindow.py b/examples/serialport/terminal/ui_mainwindow.py index 6aa0fcc5..21adbd5c 100644 --- a/examples/serialport/terminal/ui_mainwindow.py +++ b/examples/serialport/terminal/ui_mainwindow.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'mainwindow.ui' ## -## Created by: Qt User Interface Compiler version 6.4.0 +## Created by: Qt User Interface Compiler version 6.7.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -74,7 +74,7 @@ class Ui_MainWindow(object): MainWindow.setMenuBar(self.menuBar) self.mainToolBar = QToolBar(MainWindow) self.mainToolBar.setObjectName(u"mainToolBar") - MainWindow.addToolBar(Qt.TopToolBarArea, self.mainToolBar) + MainWindow.addToolBar(Qt.ToolBarArea.TopToolBarArea, self.mainToolBar) self.statusBar = QStatusBar(MainWindow) self.statusBar.setObjectName(u"statusBar") MainWindow.setStatusBar(self.statusBar) diff --git a/examples/serialport/terminal/ui_settingsdialog.py b/examples/serialport/terminal/ui_settingsdialog.py index 212b60b2..6c5a417c 100644 --- a/examples/serialport/terminal/ui_settingsdialog.py +++ b/examples/serialport/terminal/ui_settingsdialog.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'settingsdialog.ui' ## -## Created by: Qt User Interface Compiler version 6.4.0 +## Created by: Qt User Interface Compiler version 6.7.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -128,7 +128,7 @@ class Ui_SettingsDialog(object): self.horizontalLayout = QHBoxLayout() self.horizontalLayout.setObjectName(u"horizontalLayout") - self.horizontalSpacer = QSpacerItem(96, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) + self.horizontalSpacer = QSpacerItem(96, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum) self.horizontalLayout.addItem(self.horizontalSpacer) diff --git a/examples/speech/hello_speak/ui_mainwindow.py b/examples/speech/hello_speak/ui_mainwindow.py index b42d35f0..ab132e7f 100644 --- a/examples/speech/hello_speak/ui_mainwindow.py +++ b/examples/speech/hello_speak/ui_mainwindow.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'mainwindow.ui' ## -## Created by: Qt User Interface Compiler version 6.5.0 +## Created by: Qt User Interface Compiler version 6.7.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -31,7 +31,7 @@ class Ui_MainWindow(object): self.verticalLayout.setObjectName(u"verticalLayout") self.plainTextEdit = QPlainTextEdit(self.centralwidget) self.plainTextEdit.setObjectName(u"plainTextEdit") - sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + sizePolicy = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.plainTextEdit.sizePolicy().hasHeightForWidth()) @@ -43,7 +43,7 @@ class Ui_MainWindow(object): self.gridLayout.setObjectName(u"gridLayout") self.label_5 = QLabel(self.centralwidget) self.label_5.setObjectName(u"label_5") - sizePolicy1 = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred) + sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Preferred) sizePolicy1.setHorizontalStretch(0) sizePolicy1.setVerticalStretch(0) sizePolicy1.setHeightForWidth(self.label_5.sizePolicy().hasHeightForWidth()) @@ -95,7 +95,7 @@ class Ui_MainWindow(object): self.language = QComboBox(self.centralwidget) self.language.setObjectName(u"language") - sizePolicy2 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + sizePolicy2 = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) sizePolicy2.setHorizontalStretch(0) sizePolicy2.setVerticalStretch(0) sizePolicy2.setHeightForWidth(self.language.sizePolicy().hasHeightForWidth()) @@ -169,7 +169,7 @@ class Ui_MainWindow(object): self.verticalLayout.addLayout(self.horizontalLayout) - self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding) + self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding) self.verticalLayout.addItem(self.verticalSpacer) diff --git a/examples/sql/books/ui_bookwindow.py b/examples/sql/books/ui_bookwindow.py index 17589157..52795217 100644 --- a/examples/sql/books/ui_bookwindow.py +++ b/examples/sql/books/ui_bookwindow.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'bookwindow.ui' ## -## Created by: Qt User Interface Compiler version 6.2.3 +## Created by: Qt User Interface Compiler version 6.7.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ diff --git a/examples/utils/pyside_config.py b/examples/utils/pyside_config.py index e8333e2f..d3067eec 100644 --- a/examples/utils/pyside_config.py +++ b/examples/utils/pyside_config.py @@ -196,7 +196,7 @@ def find_package_path(dir_name): return None -# Return version as "3.7" +# Return version as "x.y" (e.g. 3.9, 3.12, etc) def python_version(): return str(sys.version_info[0]) + '.' + str(sys.version_info[1]) diff --git a/examples/webchannel/standalone/ui_dialog.py b/examples/webchannel/standalone/ui_dialog.py index 866d2a2e..36c2fe40 100644 --- a/examples/webchannel/standalone/ui_dialog.py +++ b/examples/webchannel/standalone/ui_dialog.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'dialog.ui' ## -## Created by: Qt User Interface Compiler version 6.2.3 +## Created by: Qt User Interface Compiler version 6.7.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ diff --git a/examples/webenginewidgets/markdowneditor/ui_mainwindow.py b/examples/webenginewidgets/markdowneditor/ui_mainwindow.py index 0705ebfd..0be76911 100644 --- a/examples/webenginewidgets/markdowneditor/ui_mainwindow.py +++ b/examples/webenginewidgets/markdowneditor/ui_mainwindow.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'mainwindow.ui' ## -## Created by: Qt User Interface Compiler version 6.2.3 +## Created by: Qt User Interface Compiler version 6.7.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ diff --git a/examples/webenginewidgets/simplebrowser/browserwindow.py b/examples/webenginewidgets/simplebrowser/browserwindow.py index 4efdfbdd..43b81120 100644 --- a/examples/webenginewidgets/simplebrowser/browserwindow.py +++ b/examples/webenginewidgets/simplebrowser/browserwindow.py @@ -45,8 +45,10 @@ class BrowserWindow(QMainWindow): self._profile = profile self._tab_widget = TabWidget(profile, self) - self._stop_icon = QIcon(":process-stop.png") - self._reload_icon = QIcon(":view-refresh.png") + self._stop_icon = QIcon.fromTheme(QIcon.ThemeIcon.ProcessStop, + QIcon(":process-stop.png")) + self._reload_icon = QIcon.fromTheme(QIcon.ThemeIcon.ViewRefresh, + QIcon(":view-refresh.png")) self.setAttribute(Qt.WA_DeleteOnClose, True) self.setFocusPolicy(Qt.ClickFocus) @@ -357,7 +359,9 @@ class BrowserWindow(QMainWindow): back_shortcuts.append(QKeySequence(Qt.Key_Back)) self._history_back_action.setShortcuts(back_shortcuts) self._history_back_action.setIconVisibleInMenu(False) - self._history_back_action.setIcon(QIcon(":go-previous.png")) + back_icon = QIcon.fromTheme(QIcon.ThemeIcon.GoPrevious, + QIcon(":go-previous.png")) + self._history_back_action.setIcon(back_icon) self._history_back_action.setToolTip("Go back in history") self._history_back_action.triggered.connect(self._back) navigation_bar.addAction(self._history_back_action) @@ -367,7 +371,9 @@ class BrowserWindow(QMainWindow): fwd_shortcuts.append(QKeySequence(Qt.Key_Forward)) self._history_forward_action.setShortcuts(fwd_shortcuts) self._history_forward_action.setIconVisibleInMenu(False) - self._history_forward_action.setIcon(QIcon(":go-next.png")) + next_icon = QIcon.fromTheme(QIcon.ThemeIcon.GoNext, + QIcon(":go-next.png")) + self._history_forward_action.setIcon(next_icon) self._history_forward_action.setToolTip("Go forward in history") self._history_forward_action.triggered.connect(self._forward) navigation_bar.addAction(self._history_forward_action) diff --git a/examples/webenginewidgets/simplebrowser/downloadwidget.py b/examples/webenginewidgets/simplebrowser/downloadwidget.py index 6f3dbf78..3b4973cb 100644 --- a/examples/webenginewidgets/simplebrowser/downloadwidget.py +++ b/examples/webenginewidgets/simplebrowser/downloadwidget.py @@ -34,8 +34,10 @@ class DownloadWidget(QFrame): self._download = download self._time_added = QElapsedTimer() self._time_added.start() - self._cancel_icon = QIcon(":process-stop.png") - self._remove_icon = QIcon(":edit-clear.png") + self._cancel_icon = QIcon.fromTheme(QIcon.ThemeIcon.ProcessStop, + QIcon(":process-stop.png")) + self._remove_icon = QIcon.fromTheme(QIcon.ThemeIcon.EditClear, + QIcon(":edit-clear.png")) self._ui = Ui_DownloadWidget() self._ui.setupUi(self) diff --git a/examples/webenginewidgets/simplebrowser/ui_certificateerrordialog.py b/examples/webenginewidgets/simplebrowser/ui_certificateerrordialog.py index bf2fef36..a963f0ac 100644 --- a/examples/webenginewidgets/simplebrowser/ui_certificateerrordialog.py +++ b/examples/webenginewidgets/simplebrowser/ui_certificateerrordialog.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'certificateerrordialog.ui' ## -## Created by: Qt User Interface Compiler version 6.5.0 +## Created by: Qt User Interface Compiler version 6.7.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -35,7 +35,7 @@ class Ui_CertificateErrorDialog(object): self.m_errorLabel = QLabel(CertificateErrorDialog) self.m_errorLabel.setObjectName(u"m_errorLabel") - sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) + sizePolicy = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.m_errorLabel.sizePolicy().hasHeightForWidth()) @@ -47,7 +47,7 @@ class Ui_CertificateErrorDialog(object): self.m_infoLabel = QLabel(CertificateErrorDialog) self.m_infoLabel.setObjectName(u"m_infoLabel") - sizePolicy1 = QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) + sizePolicy1 = QSizePolicy(QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.MinimumExpanding) sizePolicy1.setHorizontalStretch(0) sizePolicy1.setVerticalStretch(0) sizePolicy1.setHeightForWidth(self.m_infoLabel.sizePolicy().hasHeightForWidth()) @@ -57,7 +57,7 @@ class Ui_CertificateErrorDialog(object): self.verticalLayout.addWidget(self.m_infoLabel) - self.verticalSpacer = QSpacerItem(20, 16, QSizePolicy.Minimum, QSizePolicy.Expanding) + self.verticalSpacer = QSpacerItem(20, 16, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding) self.verticalLayout.addItem(self.verticalSpacer) diff --git a/examples/webenginewidgets/simplebrowser/ui_downloadmanagerwidget.py b/examples/webenginewidgets/simplebrowser/ui_downloadmanagerwidget.py index 0f98831d..f0f61aa7 100644 --- a/examples/webenginewidgets/simplebrowser/ui_downloadmanagerwidget.py +++ b/examples/webenginewidgets/simplebrowser/ui_downloadmanagerwidget.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'downloadmanagerwidget.ui' ## -## Created by: Qt User Interface Compiler version 6.5.0 +## Created by: Qt User Interface Compiler version 6.7.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -49,7 +49,7 @@ class Ui_DownloadManagerWidget(object): self.m_itemsLayout.setContentsMargins(3, 3, 3, 3) self.m_zeroItemsLabel = QLabel(self.m_items) self.m_zeroItemsLabel.setObjectName(u"m_zeroItemsLabel") - sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + sizePolicy = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.m_zeroItemsLabel.sizePolicy().hasHeightForWidth()) diff --git a/examples/webenginewidgets/simplebrowser/ui_downloadwidget.py b/examples/webenginewidgets/simplebrowser/ui_downloadwidget.py index 3522f075..58c32fdf 100644 --- a/examples/webenginewidgets/simplebrowser/ui_downloadwidget.py +++ b/examples/webenginewidgets/simplebrowser/ui_downloadwidget.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'downloadwidget.ui' ## -## Created by: Qt User Interface Compiler version 6.5.0 +## Created by: Qt User Interface Compiler version 6.7.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -40,7 +40,7 @@ class Ui_DownloadWidget(object): self.m_cancelButton = QPushButton(DownloadWidget) self.m_cancelButton.setObjectName(u"m_cancelButton") - sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + sizePolicy = QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.m_cancelButton.sizePolicy().hasHeightForWidth()) diff --git a/examples/webenginewidgets/simplebrowser/ui_passworddialog.py b/examples/webenginewidgets/simplebrowser/ui_passworddialog.py index 6a40f30e..11e0c4a2 100644 --- a/examples/webenginewidgets/simplebrowser/ui_passworddialog.py +++ b/examples/webenginewidgets/simplebrowser/ui_passworddialog.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'passworddialog.ui' ## -## Created by: Qt User Interface Compiler version 6.5.0 +## Created by: Qt User Interface Compiler version 6.7.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -34,7 +34,7 @@ class Ui_PasswordDialog(object): self.m_infoLabel = QLabel(PasswordDialog) self.m_infoLabel.setObjectName(u"m_infoLabel") - sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) + sizePolicy = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.m_infoLabel.sizePolicy().hasHeightForWidth()) diff --git a/examples/webenginewidgets/simplebrowser/webview.py b/examples/webenginewidgets/simplebrowser/webview.py index 84d24833..e1282c1d 100644 --- a/examples/webenginewidgets/simplebrowser/webview.py +++ b/examples/webenginewidgets/simplebrowser/webview.py @@ -55,7 +55,8 @@ class WebView(QWebEngineView): self.renderProcessTerminated.connect(self._render_process_terminated) self._error_icon = QIcon(":dialog-error.png") - self._loading_icon = QIcon(":view-refresh.png") + self._loading_icon = QIcon.fromTheme(QIcon.ThemeIcon.ViewRefresh, + QIcon(":view-refresh.png")) self._default_icon = QIcon(":text-html.png") @Slot() diff --git a/examples/widgetbinding/doc/widgetbinding.md b/examples/widgetbinding/doc/widgetbinding.md index 5fee93c8..910961b1 100644 --- a/examples/widgetbinding/doc/widgetbinding.md +++ b/examples/widgetbinding/doc/widgetbinding.md @@ -1,7 +1,5 @@ -# WigglyWidget - -The original Qt/C++ example can be found here: -https://doc.qt.io/qt-6/qtwidgets-widgets-wiggly-example.html +(widgetbinding-example)= +# WigglyWidget Example This example shows how to interact with a custom widget from two different ways: @@ -42,20 +40,24 @@ The most important files are: Now create a `build/` directory, and from inside run `cmake` to use the provided `CMakeLists.txt`: -macOS/Linux: +Run CMake on macOS/Linux: ```bash cd ~/pyside-setup/examples/widgetbinding +cd build +cmake .. -B. -G Ninja -DCMAKE_BUILD_TYPE=Release ``` -On Windows: +Run CMake on Windows: ```bash cd C:\pyside-setup\examples\widgetbinding +mkdir build +cd build +cmake .. -B. -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=cl.exe ``` +To build: ```bash -mkdir build -cd build -cmake -H.. -B. -G Ninja -DCMAKE_BUILD_TYPE=Release + ninja ninja install cd .. diff --git a/examples/widgets/animation/easing/ui_form.py b/examples/widgets/animation/easing/ui_form.py index 0ff7a154..2925cbae 100644 --- a/examples/widgets/animation/easing/ui_form.py +++ b/examples/widgets/animation/easing/ui_form.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- ################################################################################ -## Form generated from reading UI file 'form2.ui' +## Form generated from reading UI file 'form.ui' ## -## Created by: Qt User Interface Compiler version 6.4.0 +## Created by: Qt User Interface Compiler version 6.7.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -29,7 +29,7 @@ class Ui_Form(object): self.gridLayout.setObjectName(u"gridLayout") self.easingCurvePicker = QListWidget(Form) self.easingCurvePicker.setObjectName(u"easingCurvePicker") - sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) + sizePolicy = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.easingCurvePicker.sizePolicy().hasHeightForWidth()) @@ -73,7 +73,7 @@ class Ui_Form(object): self.groupBox = QGroupBox(Form) self.groupBox.setObjectName(u"groupBox") - sizePolicy1 = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Preferred) sizePolicy1.setHorizontalStretch(0) sizePolicy1.setVerticalStretch(0) sizePolicy1.setHeightForWidth(self.groupBox.sizePolicy().hasHeightForWidth()) @@ -83,7 +83,7 @@ class Ui_Form(object): self.formLayout.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow) self.label = QLabel(self.groupBox) self.label.setObjectName(u"label") - sizePolicy2 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) + sizePolicy2 = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred) sizePolicy2.setHorizontalStretch(0) sizePolicy2.setVerticalStretch(0) sizePolicy2.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth()) @@ -95,7 +95,7 @@ class Ui_Form(object): self.periodSpinBox = QDoubleSpinBox(self.groupBox) self.periodSpinBox.setObjectName(u"periodSpinBox") self.periodSpinBox.setEnabled(False) - sizePolicy3 = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) + sizePolicy3 = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed) sizePolicy3.setHorizontalStretch(0) sizePolicy3.setVerticalStretch(0) sizePolicy3.setHeightForWidth(self.periodSpinBox.sizePolicy().hasHeightForWidth()) @@ -142,7 +142,7 @@ class Ui_Form(object): self.verticalLayout.addWidget(self.groupBox) - self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding) + self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding) self.verticalLayout.addItem(self.verticalSpacer) @@ -151,7 +151,7 @@ class Ui_Form(object): self.graphicsView = QGraphicsView(Form) self.graphicsView.setObjectName(u"graphicsView") - sizePolicy4 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + sizePolicy4 = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) sizePolicy4.setHorizontalStretch(0) sizePolicy4.setVerticalStretch(0) sizePolicy4.setHeightForWidth(self.graphicsView.sizePolicy().hasHeightForWidth()) diff --git a/examples/widgets/linguist/linguist.qrc b/examples/widgets/linguist/linguist.qrc index 2b08ae57..6ca287c2 100644 --- a/examples/widgets/linguist/linguist.qrc +++ b/examples/widgets/linguist/linguist.qrc @@ -1,5 +1,5 @@ - - translations/example_de.qm + + example_de.qm diff --git a/examples/widgets/mainwindows/application/application.py b/examples/widgets/mainwindows/application/application.py index 7b2fa067..f69eade2 100644 --- a/examples/widgets/mainwindows/application/application.py +++ b/examples/widgets/mainwindows/application/application.py @@ -82,16 +82,16 @@ class MainWindow(QMainWindow): self.setWindowModified(self._text_edit.document().isModified()) def create_actions(self): - icon = QIcon.fromTheme("document-new", QIcon(':/images/new.png')) + icon = QIcon.fromTheme(QIcon.ThemeIcon.DocumentNew, QIcon(':/images/new.png')) self._new_act = QAction(icon, "&New", self, shortcut=QKeySequence.New, statusTip="Create a new file", triggered=self.new_file) - icon = QIcon.fromTheme("document-open", QIcon(':/images/open.png')) + icon = QIcon.fromTheme(QIcon.ThemeIcon.DocumentOpen, QIcon(':/images/open.png')) self._open_act = QAction(icon, "&Open...", self, shortcut=QKeySequence.Open, statusTip="Open an existing file", triggered=self.open) - icon = QIcon.fromTheme("document-save", QIcon(':/images/save.png')) + icon = QIcon.fromTheme(QIcon.ThemeIcon.DocumentSave, QIcon(':/images/save.png')) self._save_act = QAction(icon, "&Save", self, shortcut=QKeySequence.Save, statusTip="Save the document to disk", triggered=self.save) @@ -101,28 +101,30 @@ class MainWindow(QMainWindow): statusTip="Save the document under a new name", triggered=self.save_as) - self._exit_act = QAction("E&xit", self, shortcut="Ctrl+Q", + icon = QIcon.fromTheme(QIcon.ThemeIcon.ApplicationExit) + self._exit_act = QAction(icon, "E&xit", self, shortcut="Ctrl+Q", statusTip="Exit the application", triggered=self.close) - icon = QIcon.fromTheme("edit-cut", QIcon(':/images/cut.png')) + icon = QIcon.fromTheme(QIcon.ThemeIcon.EditCut, QIcon(':/images/cut.png')) self._cut_act = QAction(icon, "Cu&t", self, shortcut=QKeySequence.Cut, statusTip="Cut the current selection's contents to the clipboard", triggered=self._text_edit.cut) - icon = QIcon.fromTheme("edit-copy", QIcon(':/images/copy.png')) + icon = QIcon.fromTheme(QIcon.ThemeIcon.EditCopy, QIcon(':/images/copy.png')) self._copy_act = QAction(icon, "&Copy", self, shortcut=QKeySequence.Copy, statusTip="Copy the current selection's contents to the clipboard", triggered=self._text_edit.copy) - icon = QIcon.fromTheme("edit-paste", QIcon(':/images/paste.png')) + icon = QIcon.fromTheme(QIcon.ThemeIcon.EditPaste, QIcon(':/images/paste.png')) self._paste_act = QAction(icon, "&Paste", self, shortcut=QKeySequence.Paste, statusTip="Paste the clipboard's contents into the current " "selection", triggered=self._text_edit.paste) - self._about_act = QAction("&About", self, + icon = QIcon.fromTheme(QIcon.ThemeIcon.HelpAbout) + self._about_act = QAction(icon, "&About", self, statusTip="Show the application's About box", triggered=self.about) diff --git a/examples/widgets/mainwindows/mdi/mdi.py b/examples/widgets/mainwindows/mdi/mdi.py index 92c0c9c2..c1fa01b8 100644 --- a/examples/widgets/mainwindows/mdi/mdi.py +++ b/examples/widgets/mainwindows/mdi/mdi.py @@ -271,17 +271,17 @@ class MainWindow(QMainWindow): def create_actions(self): - icon = QIcon.fromTheme("document-new") + icon = QIcon.fromTheme(QIcon.ThemeIcon.DocumentNew) self._new_act = QAction(icon, "&New", self, shortcut=QKeySequence.New, statusTip="Create a new file", triggered=self.new_file) - icon = QIcon.fromTheme("document-open") + icon = QIcon.fromTheme(QIcon.ThemeIcon.DocumentOpen) self._open_act = QAction(icon, "&Open...", self, shortcut=QKeySequence.Open, statusTip="Open an existing file", triggered=self.open) - icon = QIcon.fromTheme("document-save") + icon = QIcon.fromTheme(QIcon.ThemeIcon.DocumentSave) self._save_act = QAction(icon, "&Save", self, shortcut=QKeySequence.Save, statusTip="Save the document to disk", triggered=self.save) @@ -291,23 +291,24 @@ class MainWindow(QMainWindow): statusTip="Save the document under a new name", triggered=self.save_as) - self._exit_act = QAction("E&xit", self, shortcut=QKeySequence.Quit, + icon = QIcon.fromTheme(QIcon.ThemeIcon.ApplicationExit) + self._exit_act = QAction(icon, "E&xit", self, shortcut=QKeySequence.Quit, statusTip="Exit the application", triggered=QApplication.instance().closeAllWindows) - icon = QIcon.fromTheme("edit-cut") + icon = QIcon.fromTheme(QIcon.ThemeIcon.EditCut) self._cut_act = QAction(icon, "Cu&t", self, shortcut=QKeySequence.Cut, statusTip="Cut the current selection's contents to the clipboard", triggered=self.cut) - icon = QIcon.fromTheme("edit-copy") + icon = QIcon.fromTheme(QIcon.ThemeIcon.EditCopy) self._copy_act = QAction(icon, "&Copy", self, shortcut=QKeySequence.Copy, statusTip="Copy the current selection's contents to the clipboard", triggered=self.copy) - icon = QIcon.fromTheme("edit-paste") + icon = QIcon.fromTheme(QIcon.ThemeIcon.EditPaste) self._paste_act = QAction(icon, "&Paste", self, shortcut=QKeySequence.Paste, statusTip="Paste the clipboard's contents into the current " @@ -341,7 +342,8 @@ class MainWindow(QMainWindow): self._separator_act = QAction(self) self._separator_act.setSeparator(True) - self._about_act = QAction("&About", self, + icon = QIcon.fromTheme(QIcon.ThemeIcon.HelpAbout) + self._about_act = QAction(icon, "&About", self, statusTip="Show the application's About box", triggered=self.about) diff --git a/examples/widgets/rhi/simplerhiwidget/doc/simplerhiwidget.rst b/examples/widgets/rhi/simplerhiwidget/doc/simplerhiwidget.rst new file mode 100644 index 00000000..c33c6660 --- /dev/null +++ b/examples/widgets/rhi/simplerhiwidget/doc/simplerhiwidget.rst @@ -0,0 +1,34 @@ +.. _rhi-widget-example: + +Simple RHI Widget Example +========================= + +Shows how to render a triangle using ``QRhi``, Qt's 3D API and shading +language abstraction layer. + +This example is, in many ways, the counterpart of the :ref:`rhi-window-example` +in the QWidget world. The ``QRhiWidget`` subclass in this applications renders +a single triangle, using a simple graphics pipeline with basic vertex and +fragment shaders. Unlike the plain ``QWindow``-based application, this example +does not need to worry about lower level details, such as setting up the window +and the ``QRhi``, or dealing with swapchain and window events, as that is taken +care of by the ``QWidget`` framework here. The instance of the ``QRhiWidget`` +subclass is added to a ``QVBoxLayout``. To keep the example minimal and +compact, there are no further widgets or 3D content introduced. + +Once an instance of ``ExampleRhiWidget``, a ``QRhiWidget`` subclass, is added +to a top-level widget's child hierarchy, the corresponding window automatically +becomes a Direct 3D, Vulkan, Metal, or OpenGL-rendered window. The +``QPainter``-rendered widget content, i.e. everything that is not a +``QRhiWidget``, ``QOpenGLWidget``, or ``QQuickWidget``, is then uploaded to a +texture, whereas the mentioned special widgets each render to a texture. The +resulting set textures is composited together by the top-level widget's +backingstore. + +As opposed to the C++ example, the cleanup is done by reimplementing +``QRhiWidget.releaseResources()``, which is called from QWidget.closeEvent() of +the top level widget to ensure a deterministic cleanup sequence. + +.. image:: simplerhiwidget.webp + :width: 400 + :alt: Screenshot of the Simple RHI Widget example diff --git a/examples/widgets/rhi/simplerhiwidget/doc/simplerhiwidget.webp b/examples/widgets/rhi/simplerhiwidget/doc/simplerhiwidget.webp new file mode 100644 index 00000000..fdb8d20e Binary files /dev/null and b/examples/widgets/rhi/simplerhiwidget/doc/simplerhiwidget.webp differ diff --git a/examples/widgets/rhi/simplerhiwidget/examplewidget.py b/examples/widgets/rhi/simplerhiwidget/examplewidget.py new file mode 100644 index 00000000..5b3e40f5 --- /dev/null +++ b/examples/widgets/rhi/simplerhiwidget/examplewidget.py @@ -0,0 +1,135 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import numpy + +from PySide6.QtCore import (QFile, QIODevice) +from PySide6.QtGui import (QColor, QMatrix4x4) +from PySide6.QtGui import (QRhiBuffer, + QRhiDepthStencilClearValue, + QRhiShaderResourceBinding, + QRhiShaderStage, + QRhiVertexInputAttribute, QRhiVertexInputBinding, + QRhiVertexInputLayout, QRhiViewport, + QShader) +from PySide6.QtWidgets import QRhiWidget +from PySide6.support import VoidPtr + +VERTEX_DATA = numpy.array([ 0.0, 0.5, 1.0, 0.0, 0.0, # noqa E:201 + -0.5, -0.5, 0.0, 1.0, 0.0, # noqa E:241 + 0.5, -0.5, 0.0, 0.0, 1.0], + dtype=numpy.float32) + + +def getShader(name): + f = QFile(name) + if f.open(QIODevice.ReadOnly): + return QShader.fromSerialized(f.readAll()) + return QShader() + + +class ExampleRhiWidget(QRhiWidget): + + def __init__(self, parent=None): + super().__init__(parent) + self.m_rhi = None + self.m_vbuf = None + self.m_ubuf = None + self.m_srb = None + self.m_pipeline = None + self.m_viewProjection = QMatrix4x4() + self.m_rotation = 0.0 + + def releaseResources(self): + self.m_pipeline.destroy() + del self.m_pipeline + self.m_pipeline = None + self.m_srb.destroy() + del self.m_srb + self.m_srb = None + self.m_ubuf.destroy() + del self.m_ubuf + self.m_ubuf = None + self.m_vbuf.destroy() + del self.m_vbuf + self.m_buf = None + + def initialize(self, cb): + if self.m_rhi != self.rhi(): + self.m_pipeline = None + self.m_rhi = self.rhi() + + if not self.m_pipeline: + vertex_size = 4 * VERTEX_DATA.size + self.m_vbuf = self.m_rhi.newBuffer(QRhiBuffer.Immutable, + QRhiBuffer.VertexBuffer, vertex_size) + self.m_vbuf.create() + + self.m_ubuf = self.m_rhi.newBuffer(QRhiBuffer.Dynamic, + QRhiBuffer.UniformBuffer, 64) + self.m_ubuf.create() + + self.m_srb = self.m_rhi.newShaderResourceBindings() + bindings = [ + QRhiShaderResourceBinding.uniformBuffer(0, QRhiShaderResourceBinding.VertexStage, + self.m_ubuf) + ] + self.m_srb.setBindings(bindings) + self.m_srb.create() + + self.m_pipeline = self.m_rhi.newGraphicsPipeline() + stages = [ + QRhiShaderStage(QRhiShaderStage.Vertex, + getShader(":/shader_assets/color.vert.qsb")), + QRhiShaderStage(QRhiShaderStage.Fragment, + getShader(":/shader_assets/color.frag.qsb")) + ] + self.m_pipeline.setShaderStages(stages) + inputLayout = QRhiVertexInputLayout() + input_bindings = [QRhiVertexInputBinding(5 * 4)] # sizeof(float) + inputLayout.setBindings(input_bindings) + attributes = [ # 4: sizeof(float) + QRhiVertexInputAttribute(0, 0, QRhiVertexInputAttribute.Float2, 0), + QRhiVertexInputAttribute(0, 1, QRhiVertexInputAttribute.Float3, 2 * 4) + ] + inputLayout.setAttributes(attributes) + self.m_pipeline.setVertexInputLayout(inputLayout) + self.m_pipeline.setShaderResourceBindings(self.m_srb) + self.m_pipeline.setRenderPassDescriptor(self.renderTarget().renderPassDescriptor()) + self.m_pipeline.create() + + resourceUpdates = self.m_rhi.nextResourceUpdateBatch() + resourceUpdates.uploadStaticBuffer(self.m_vbuf, VoidPtr(VERTEX_DATA.tobytes(), + vertex_size)) + cb.resourceUpdate(resourceUpdates) + + outputSize = self.renderTarget().pixelSize() + self.m_viewProjection = self.m_rhi.clipSpaceCorrMatrix() + r = float(outputSize.width()) / float(outputSize.height()) + self.m_viewProjection.perspective(45.0, r, 0.01, 1000.0) + self.m_viewProjection.translate(0, 0, -4) + + def render(self, cb): + resourceUpdates = self.m_rhi.nextResourceUpdateBatch() + self.m_rotation += 1.0 + modelViewProjection = self.m_viewProjection + modelViewProjection.rotate(self.m_rotation, 0, 1, 0) + projection = numpy.array(modelViewProjection.data(), + dtype=numpy.float32) + resourceUpdates.updateDynamicBuffer(self.m_ubuf, 0, 64, + projection.tobytes()) + clearColor = QColor.fromRgbF(0.4, 0.7, 0.0, 1.0) + cv = QRhiDepthStencilClearValue(1.0, 0) + cb.beginPass(self.renderTarget(), clearColor, cv, resourceUpdates) + + cb.setGraphicsPipeline(self.m_pipeline) + outputSize = self.renderTarget().pixelSize() + cb.setViewport(QRhiViewport(0, 0, outputSize.width(), + outputSize.height())) + cb.setShaderResources() + vbufBinding = (self.m_vbuf, 0) + cb.setVertexInput(0, [vbufBinding]) + cb.draw(3) + cb.endPass() + + self.update() diff --git a/examples/widgets/rhi/simplerhiwidget/main.py b/examples/widgets/rhi/simplerhiwidget/main.py new file mode 100644 index 00000000..59be81dd --- /dev/null +++ b/examples/widgets/rhi/simplerhiwidget/main.py @@ -0,0 +1,35 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +"""PySide6 port of the Qt Simple RHI Widget Example example from Qt v6.x""" + +import sys + +from PySide6.QtWidgets import QApplication, QVBoxLayout, QWidget + +from examplewidget import ExampleRhiWidget +import rc_simplerhiwidget # noqa F:401 + + +class Widget(QWidget): + + def __init__(self, parent=None): + super().__init__(parent) + layout = QVBoxLayout(self) + self._rhi_widget = ExampleRhiWidget(self) + layout.addWidget(self._rhi_widget) + + def closeEvent(self, e): + self._rhi_widget.releaseResources() + e.accept() + + +if __name__ == "__main__": + app = QApplication(sys.argv) + + w = Widget() + w.resize(1280, 720) + w.show() + exit_code = app.exec() + del w + sys.exit(exit_code) diff --git a/examples/widgets/rhi/simplerhiwidget/rc_simplerhiwidget.py b/examples/widgets/rhi/simplerhiwidget/rc_simplerhiwidget.py new file mode 100644 index 00000000..ee8b4124 --- /dev/null +++ b/examples/widgets/rhi/simplerhiwidget/rc_simplerhiwidget.py @@ -0,0 +1,163 @@ +# Resource object code (Python 3) +# Created by: object code +# Created by: The Resource Compiler for Qt version 6.7.0 +# WARNING! All changes made in this file will be lost! + +from PySide6 import QtCore + +qt_resource_data = b"\ +\x00\x00\x02\xe2\ +\x00\ +\x00\x07\xc4x\x9c\xb5T]k\xd4@\x14\x9d\xed\xa6]\ +\x9b\xd6\xda\x0f\xd07\x19\xe9\xcb\x16%\xa6\xb5\x15q\xdd\ +\xfa\xb0U)\x14Z\xdbR\x84e\x091\x9bM\x07\xb2\ +\xc9\x92\x8fE)\x05\xdf}\xf7w\xf8'\xfcO\xbe\x88\ +\xde;s\xd3\xcc\xa6[*\x88\x03\xb3\x999s\xef=\ +g\xceM\x9616\xcf\x183`\xd6`.\xb11s\ +\x98\xc7b\x16\xc2L\x00\xa9\xc3d\xbfi\xb0)\x03\xd3\ +V\xd9\x00\x82]\x16\xb0\x8e\x96j\xdc\x96\xfa\xafc\x8e\ +\x9e}M\xcd\xf7\xfa\xccz\x03U\x99\xec\x0e[#t\ +\x85\xcdH\x9d\x0b\x90\x82\xcfw\x07'\x07V\x9a\xf5\xad\ +\xed\x1d\x1b\xcf\x97\xd45\xe5\xd9=\xd6\x90\xcaq\x0e]\ +\x11!\x8e\x16-\xc2\x5c\x868\xc4\x1bX\x03V\x88?\ +$\x0e\xdc/j\xfb5\xe0\x9c\x81\xe7#\xc8\xc1\xea\xb8\ +\xbe\x0f+\x14\xcda>\x80\xf8\x06]\xc2\x90\x98\x01\x8a\ +\x95\xe1\x88\xb7h?O\x18\xc6\x9b\x14_\xa7\xf8\x05\xd2\ +lR\xfc\x02\xe9D\xec1\xec\xe7\xe4\xddp|y\xfd\ +\x9c\xcdJ\x0d\x06\xe9\xc3\x1a?\x01\x99\x85g\x9bj\xdf\ +\xa5\xfc\xf7\x80\xceI/\x14\xc64l\x99\xb0\x9a\x86\xad\ +\x10\x86\xf5\x8f@=\xea_\xa5\xfce:G\x1d\xbb\xc0\ +:Og\xbf\xa0\xc2\x0bY\x85\x19\xe4s\x8dzY#\ +\xca\xaf\xebc?IE\x1c\xf1M\xdb6G\x89\xef\x09\ +\xb9;\x17\xc1\xf9\x88\x0f\xc2\xd8\xcdZ\xd7`\x11\x01h\ +\x8e\xdd\xe4\xb3\x88\x02>\xf6\xbdg|\xecxq\x18'\ +\x08\xc7\xa2\xcf\x91\xad\xb9a^\x98\x1cF\x10:o\x13\ +7\xd8s3\xb7k\xf7x\x1b3\xb6\x9b\x94\xf1\x84o\ +Z\xf6F\xcb\xbc4\xcd\x8a\xccO\xe5+\xc7\x9cR\xe6\ +\x96\xfd\xff\xa9\xbfi\xd4\xa2\xa4\xde\x01\xea8\xcfd\x11\ +>\x80\xba\x1dE,\xa2\xdb\x95\x5c\x85\xff\x95\x08\xec\xf2\ +V\xa9\xa1\xf6#\xcd\xdcLx\xaa!\x13\xdc\xfa\x81\xae\ + \xcd\x92\xdc\xcb\xf8\xc9\xd1\xfe\xf1\x99\xd3I\xe24u\ +\xf6\xa3Q\x9e\x15r&\xe2\xf9K~\xfa\xe6C\xe7\xf0\ +\xf0x\xcf\x06)\xd3\xd3\x0f\xf3\xac\x92\xaf\x09\x81\x0a'\ +g\xce\xa9\x9b\x04~F%\xa4\x07\x18\xe0\xdch\x84\xaa\ +2a\xc5@yq\x9dX\xd9y\xed>\x1c\x0c\x08|\ +G\xe0\xba\xa0(n\xd5\xd6\x0f\xad+o\x0a\x15\xa4K\ +\x01S\x08Ur,7*HG\xac\x89\x8b\x94\x0d\xc1\ +\xb8\xc4\xcf\xf2$\xaa\x14\xb8\xd4\x1bl\xd0\xdf@\xd1\xe0\ +\xfdu\x11ya\xde\xf7\xf9\xab\xa1\x9f\xb9\xa1\x03\x7f\x9d\ +\xa1\xf8\xb8k\x96x*\x86\xfd\xa7\xf8c\x9d\xef\x9af\ +\x9e\xe2\x07\x10\xb9C?\x1d\xb9\x9e\xcfeV\xd98\xa4\ +\xb1\x91\xfa\xa6~u\xbb\xd2\x8d\xa6\xbd\xd1\xebM\xb4\x5c\ +e\x8ah\xfa\x8b\xd2\xed\xe6\xa9\x9f4\xc3\xd8\x8b\xcaL\ +\xac:\xf4#\x8dU\xad\x9aE-\xf8\xc7\x80\xcc\xa2\x19\ +\xbd^\xd1\xa82\x1eg\x9b_\x5c*\xff\xe2\xaa\xbd\xea\ +=\x11\x91U\xf9j4\xb3c\xe51\x9a<+\xeb\xd2\ +gT\xf9\x94\xe4\xa8\xb8_\xb6\xe1\xa6\xb3?Y\xbd\xa1\ +\xc8\ +\x00\x00\x04C\ +\x00\ +\x00\x0c\xf4x\x9c\xddW[o\x1bE\x14\x1e_\x92\x92\ +-\xb9\xb4i\x93\x16\xda2\xa9%dCe\x9c4A\ +\xa8&\x11\xa8 Z\xa9R\xaa\x06UHV\xb4Z\xaf\ +\xd7\xe9\xc2^\xac\xddY7\xa8\xf2\x8f\xe0\x85\xbf\xc3o\ +\xe0o\xf0\xc8\x13/U9g\xe6\xcc\xee\xec\xda\xa1*\ +\x0f<0\xd2d\xe7\x5c\xe6|\xe7|sf\x920\xc6\ +V\x98\x1au\x98\x16sY\xcc\x02\x98\x09H\x0d\x98\xb5\ +74\xd8\x82\xb1\xc1&\xe0\x9a2\x9f\x09\x981\x8b@\ +\xd7D\xc3?m\xaa\xc1\x5ccSf\xcfa\xbdu\xdb\ +2\x1b\xb2\x8c\x8d\xe5\xcaf{\xec\x00V_\xcd\xf9\x84\ +\x10|\x02\xab\xcb\xa45=6\x16\x85~\x97Q\xa3o\ +\x93\xbe\xcb\xf4\x1d\x15.\x8d\x17\x8dz\xeb\x12\xbaZ\xec\ +=\xd6\x22\xed\x15 \xb8&\xb3Z\x96\xdf\xef\x9e\x9c<\ +\xe9\xa6b\xd4\xdd?\xe8\xa1}MQ m\xebt(\ +\x08\x12:>\xd2*5X\xd1&\xcc;\xb8\x1f\xac+\ +\xb4f$_&\x19c\xb7(\xf6\x0d\xe2e\x9bb\xa3|\x93th\ +\xff \xe7M\xc9\xb7\x09g\x95\xfco\xb3\xa2W\xd0\xbe\ +Cy\xa1\xfds\xa8\xa7\xce\x8a&G\xfd_\xa0\xc1\xf3\ +?$\xee\xdf'\xfe\x8e\xc0\xbaB\xf2\xd7\xe0\x81\xb8\x1f\ +R.\xd7\xc9\x7fKr\xa6\xf4\x87\xc4\xf5G\x84\xff\x0b\ +\xecY%\x1en\x91\x1e\xe3\xecP\xbflR\x1c\xc4\xb9\ +K~\xaf!\xeb/\xd4Ul\xd2\xfd@a\xc4\x8a\xfb\ +\xf9{k\xea%\xa9\x1fG|\xb7\xd7\xb3\xacT$\x99\ ++\xf80\x1b[\xaf,\x0e#t\xc4>\x0f\xa7\x93\xbe\ +5\xeb[V\x16\xf9\xe38\x09\xd1\xce\xed\xbd\x03\xd0L\ +\x9d\xe4g?:\xe3S\xcf\xbd\xcf\xa7\xb6\x1b\x07q\xd2\ +\xb7\x1c!\x12\x7f\x98\x09O\xe9\x17i\xf7\xf9$N}\ +\x01\xc8\x18%\xf6G\x1c3lw\x08\x97B\xf1C\xbd\ +\x19\x95g\x81\xfd\x946\x81\x01\x12\xe8Bf\xfc\x13#\ +\xd2\xcc\xb2*\xc5\x9e\xb3b\x18\xc5\xee\xfd/\x8b\xfd\xd5\ +(\xf6\xb7\xa2\xd8\x83\x7fSl\x9c\x89J\xa1~T\xaa\ +P\x89\xffEix\xc7\xf6\x8a\xca\xea\x7f\xb8\x90\xe6\xd8\ +Kd\xb6\x0fx\xe2\x9d\xf9\xa9\xf0\x92\xf6\xb0\xa7a\x93\ +\xf8\xa5\x1d:?\x02\xf08\x88\xa1\xd0\xf3}\x84\xb1\x11\ +\xe6\x01\x9f8\xeeO\xf1x\x9cz\xa2\xed\xf6:\xaa~\ +\xe0\xc7\x11\xbeK\xeef\x86\xfd\x92\xc9\xa0\xa3\xac^\xa0\ +,QC\xfc\x9f<}\xfc\xec\xb9\xfd0\x89\xd3\xd4~\ +\x1cM2A\x09W6@\x96\xdf\x7f\xfb\xc3\xc3\xe3\xe3\ +g\xdf\xf4\xfa\x85\x03\xc1\x18\xd6]\x95\xfe\x82\xe8\xc7\x99\ +\xa8\x84\xcfs\xbf z\xa9l\xf09yn\x900\xd3\ +\xc7\x0b]%\xecw=\xe30\x0b\xda\xba\xb6{\xfa(\ +:\xf2\x9c\xe7sV\x1d4\xc7\x14\x07n\xcf<\xdb\xc7\ +\xb5\x86\xd6\xc0\x86\xa9k$1)20=\x8ac\x91\ +\xe9\x17\x05)\xc5\x82\x8c\xd4\xeeX\x0a\xca\xc9\xd4t\xcb\ +\xc5\x96\x9ag\xce\xb7`+\xef$\xd9\xb1\x9e\xc8\x92\xa8\ +\x0243oA\x93~{\xd0\xa8\xfd\xd9\xf2#7\xc8\ +F\x1e\xff2\xf4\x84\x13\xd8\xf0WR\xe0\x0f\x8f\xacB\ +\x9f\xfa\xe1\xe83\xfc\xd1}q\x04w<\xc5\xc7+r\ +B/\x85+\xe0q\xb9\xab\xbf\xe0i\xc8\xefL\xfe<\ +\x90\x0bf\xd2\xc3\xec\x16\xb7\xd5`\x90\xa5p\x0d\x83\xd8\ +\x8dz\x9d\xd3\xd3\x0b[k0\xd0'\x80Ns\xf1\xfd\ +\xe8\x82K1\x18\xe4\xafj\xbb\x02\xa0o\x86\xe9\xb2\xdb\ +\xd1\xe1\xf1\x88\xbd\xf3\x22}\xb5jk8\xeecl\xdd\ +!\xa7\xa7\xf7 X\x04b$I\xf9\x18\xfb\x15\xec\xea\ +\xcd\x91\xc0\x9d\xfc\x0d\xd5\x01q\x1e\xf2W3\x95R\x5c\ +:g?2{2\x9e\xeb\x97\xe2\x01\x04\xcfroR\ +W\xc4\xaa\x19\xb0\x1b\x96$&=\x8a\x95\x871\xff\xad\ +\xce\xe8\xbf\x83J\xcb\xcc\xd9\xb5\xae\xea'\xc7\xdf%\xe1\ +\xb4\xa2\ +" + +qt_resource_name = b"\ +\x00\x0d\ +\x06E\xc5\xd3\ +\x00s\ +\x00h\x00a\x00d\x00e\x00r\x00_\x00a\x00s\x00s\x00e\x00t\x00s\ +\x00\x0e\ +\x04\x16\xeb\xb2\ +\x00c\ +\x00o\x00l\x00o\x00r\x00.\x00f\x00r\x00a\x00g\x00.\x00q\x00s\x00b\ +\x00\x0e\ +\x00\xfb\xe9\x92\ +\x00c\ +\x00o\x00l\x00o\x00r\x00.\x00v\x00e\x00r\x00t\x00.\x00q\x00s\x00b\ +" + +qt_resource_struct = b"\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x02\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00B\x00\x00\x00\x00\x00\x01\x00\x00\x02\xe6\ +\x00\x00\x01\x8a!\x0c\xa5\xeb\ +\x00\x00\x00 \x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ +\x00\x00\x01\x8a!\x0c\xa5\xeb\ +" + +def qInitResources(): + QtCore.qRegisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) + +def qCleanupResources(): + QtCore.qUnregisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) + +qInitResources() diff --git a/examples/widgets/rhi/simplerhiwidget/shader_assets/color.frag.qsb b/examples/widgets/rhi/simplerhiwidget/shader_assets/color.frag.qsb new file mode 100644 index 00000000..32bd2d59 Binary files /dev/null and b/examples/widgets/rhi/simplerhiwidget/shader_assets/color.frag.qsb differ diff --git a/examples/widgets/rhi/simplerhiwidget/shader_assets/color.vert.qsb b/examples/widgets/rhi/simplerhiwidget/shader_assets/color.vert.qsb new file mode 100644 index 00000000..bf97035d Binary files /dev/null and b/examples/widgets/rhi/simplerhiwidget/shader_assets/color.vert.qsb differ diff --git a/examples/widgets/rhi/simplerhiwidget/shaders/color.frag b/examples/widgets/rhi/simplerhiwidget/shaders/color.frag new file mode 100644 index 00000000..37558766 --- /dev/null +++ b/examples/widgets/rhi/simplerhiwidget/shaders/color.frag @@ -0,0 +1,10 @@ +#version 440 + +layout(location = 0) in vec3 v_color; + +layout(location = 0) out vec4 fragColor; + +void main() +{ + fragColor = vec4(v_color, 1.0); +} diff --git a/examples/widgets/rhi/simplerhiwidget/shaders/color.vert b/examples/widgets/rhi/simplerhiwidget/shaders/color.vert new file mode 100644 index 00000000..e876f290 --- /dev/null +++ b/examples/widgets/rhi/simplerhiwidget/shaders/color.vert @@ -0,0 +1,16 @@ +#version 440 + +layout(location = 0) in vec4 position; +layout(location = 1) in vec3 color; + +layout(location = 0) out vec3 v_color; + +layout(std140, binding = 0) uniform buf { + mat4 mvp; +}; + +void main() +{ + v_color = color; + gl_Position = mvp * position; +} diff --git a/examples/widgets/rhi/simplerhiwidget/simplerhiwidget.pyproject b/examples/widgets/rhi/simplerhiwidget/simplerhiwidget.pyproject new file mode 100644 index 00000000..ff0d6275 --- /dev/null +++ b/examples/widgets/rhi/simplerhiwidget/simplerhiwidget.pyproject @@ -0,0 +1,4 @@ +{ + "files": ["main.py","examplewidget.py", "simplerhiwidget.qrc", + "shaders/color.frag", "shaders/color.vert"] +} diff --git a/examples/widgets/rhi/simplerhiwidget/simplerhiwidget.qrc b/examples/widgets/rhi/simplerhiwidget/simplerhiwidget.qrc new file mode 100644 index 00000000..ddc6dfbe --- /dev/null +++ b/examples/widgets/rhi/simplerhiwidget/simplerhiwidget.qrc @@ -0,0 +1,6 @@ + + + shader_assets/color.vert.qsb + shader_assets/color.frag.qsb + + diff --git a/examples/widgets/richtext/textedit/textedit.py b/examples/widgets/richtext/textedit/textedit.py index a3940ae0..8019446f 100644 --- a/examples/widgets/richtext/textedit/textedit.py +++ b/examples/widgets/richtext/textedit/textedit.py @@ -101,13 +101,14 @@ class TextEdit(QMainWindow): tb = self.addToolBar("File self.actions") menu = self.menuBar().addMenu("&File") - icon = QIcon.fromTheme("document-new", QIcon(RSRC_PATH + "/filenew.png")) + icon = QIcon.fromTheme(QIcon.ThemeIcon.DocumentNew, + QIcon(RSRC_PATH + "/filenew.png")) a = menu.addAction(icon, "&New", self.file_new) tb.addAction(a) a.setPriority(QAction.LowPriority) a.setShortcut(QKeySequence.New) - icon = QIcon.fromTheme("document-open", + icon = QIcon.fromTheme(QIcon.ThemeIcon.DocumentOpen, QIcon(RSRC_PATH + "/fileopen.png")) a = menu.addAction(icon, "&Open...", self.file_open) a.setShortcut(QKeySequence.Open) @@ -115,7 +116,7 @@ class TextEdit(QMainWindow): menu.addSeparator() - icon = QIcon.fromTheme("document-save", + icon = QIcon.fromTheme(QIcon.ThemeIcon.DocumentSave, QIcon(RSRC_PATH + "/filesave.png")) self._action_save = menu.addAction(icon, "&Save", self.file_save) self._action_save.setShortcut(QKeySequence.Save) @@ -126,7 +127,7 @@ class TextEdit(QMainWindow): a.setPriority(QAction.LowPriority) menu.addSeparator() - icon = QIcon.fromTheme("document-print", + icon = QIcon.fromTheme(QIcon.ThemeIcon.DocumentPrint, QIcon(RSRC_PATH + "/fileprint.png")) a = menu.addAction(icon, "&Print...", self.file_print) a.setPriority(QAction.LowPriority) @@ -151,32 +152,36 @@ class TextEdit(QMainWindow): tb = self.addToolBar("Edit self.actions") menu = self.menuBar().addMenu("&Edit") - icon = QIcon.fromTheme("edit-undo", + icon = QIcon.fromTheme(QIcon.ThemeIcon.EditUndo, QIcon(RSRC_PATH + "/editundo.png")) self._action_undo = menu.addAction(icon, "&Undo", self._text_edit.undo) self._action_undo.setShortcut(QKeySequence.Undo) tb.addAction(self._action_undo) - icon = QIcon.fromTheme("edit-redo", QIcon(RSRC_PATH + "/editredo.png")) + icon = QIcon.fromTheme(QIcon.ThemeIcon.EditRedo, + QIcon(RSRC_PATH + "/editredo.png")) self._action_redo = menu.addAction(icon, "&Redo", self._text_edit.redo) self._action_redo.setPriority(QAction.LowPriority) self._action_redo.setShortcut(QKeySequence.Redo) tb.addAction(self._action_redo) menu.addSeparator() - icon = QIcon.fromTheme("edit-cut", QIcon(RSRC_PATH + "/editcut.png")) + icon = QIcon.fromTheme(QIcon.ThemeIcon.EditCut, + QIcon(RSRC_PATH + "/editcut.png")) self._action_cut = menu.addAction(icon, "Cu&t", self._text_edit.cut) self._action_cut.setPriority(QAction.LowPriority) self._action_cut.setShortcut(QKeySequence.Cut) tb.addAction(self._action_cut) - icon = QIcon.fromTheme("edit-copy", QIcon(RSRC_PATH + "/editcopy.png")) + icon = QIcon.fromTheme(QIcon.ThemeIcon.EditCopy, + QIcon(RSRC_PATH + "/editcopy.png")) self._action_copy = menu.addAction(icon, "&Copy", self._text_edit.copy) self._action_copy.setPriority(QAction.LowPriority) self._action_copy.setShortcut(QKeySequence.Copy) tb.addAction(self._action_copy) - icon = QIcon.fromTheme("edit-paste", QIcon(RSRC_PATH + "/editpaste.png")) + icon = QIcon.fromTheme(QIcon.ThemeIcon.EditPaste, + QIcon(RSRC_PATH + "/editpaste.png")) self._action_paste = menu.addAction(icon, "&Paste", self._text_edit.paste) self._action_paste.setPriority(QAction.LowPriority) self._action_paste.setShortcut(QKeySequence.Paste) @@ -190,7 +195,7 @@ class TextEdit(QMainWindow): tb = self.addToolBar("Format self.actions") menu = self.menuBar().addMenu("F&ormat") - icon = QIcon.fromTheme("format-text-bold", + icon = QIcon.fromTheme(QIcon.ThemeIcon.FormatTextBold, QIcon(RSRC_PATH + "/textbold.png")) self._action_text_bold = menu.addAction(icon, "&Bold", self.text_bold) self._action_text_bold.setShortcut(Qt.CTRL | Qt.Key_B) @@ -201,7 +206,7 @@ class TextEdit(QMainWindow): tb.addAction(self._action_text_bold) self._action_text_bold.setCheckable(True) - icon = QIcon.fromTheme("format-text-italic", + icon = QIcon.fromTheme(QIcon.ThemeIcon.FormatTextItalic, QIcon(RSRC_PATH + "/textitalic.png")) self._action_text_italic = menu.addAction(icon, "&Italic", self.text_italic) self._action_text_italic.setPriority(QAction.LowPriority) @@ -212,7 +217,7 @@ class TextEdit(QMainWindow): tb.addAction(self._action_text_italic) self._action_text_italic.setCheckable(True) - icon = QIcon.fromTheme("format-text-underline", + icon = QIcon.fromTheme(QIcon.ThemeIcon.FormatTextUnderline, QIcon(RSRC_PATH + "/textunder.png")) self._action_text_underline = menu.addAction(icon, "&Underline", self.text_underline) @@ -226,36 +231,36 @@ class TextEdit(QMainWindow): menu.addSeparator() - icon = QIcon.fromTheme("format-justify-left", + icon = QIcon.fromTheme(QIcon.ThemeIcon.FormatJustifyLeft, QIcon(RSRC_PATH + "/textleft.png")) self._action_align_left = QAction(icon, "&Left", self) self._action_align_left.setShortcut(Qt.CTRL | Qt.Key_L) self._action_align_left.setCheckable(True) self._action_align_left.setPriority(QAction.LowPriority) - icon = QIcon.fromTheme("format-justify-center", + icon = QIcon.fromTheme(QIcon.ThemeIcon.FormatJustifyCenter, QIcon(RSRC_PATH + "/textcenter.png")) self._action_align_center = QAction(icon, "C&enter", self) self._action_align_center.setShortcut(Qt.CTRL | Qt.Key_E) self._action_align_center.setCheckable(True) self._action_align_center.setPriority(QAction.LowPriority) - icon = QIcon.fromTheme("format-justify-right", + icon = QIcon.fromTheme(QIcon.ThemeIcon.FormatJustifyRight, QIcon(RSRC_PATH + "/textright.png")) self._action_align_right = QAction(icon, "&Right", self) self._action_align_right.setShortcut(Qt.CTRL | Qt.Key_R) self._action_align_right.setCheckable(True) self._action_align_right.setPriority(QAction.LowPriority) - icon = QIcon.fromTheme("format-justify-fill", + icon = QIcon.fromTheme(QIcon.ThemeIcon.FormatJustifyFill, QIcon(RSRC_PATH + "/textjustify.png")) self._action_align_justify = QAction(icon, "&Justify", self) self._action_align_justify.setShortcut(Qt.CTRL | Qt.Key_J) self._action_align_justify.setCheckable(True) self._action_align_justify.setPriority(QAction.LowPriority) - icon = QIcon.fromTheme("format-indent-more", + icon = QIcon.fromTheme(QIcon.ThemeIcon.FormatIndentMore, QIcon(RSRC_PATH + "/format-indent-more.png")) self._action_indent_more = menu.addAction(icon, "&Indent", self.indent) self._action_indent_more.setShortcut(Qt.CTRL | Qt.Key_BracketRight) self._action_indent_more.setPriority(QAction.LowPriority) - icon = QIcon.fromTheme("format-indent-less", + icon = QIcon.fromTheme(QIcon.ThemeIcon.FormatIndentLess, QIcon(RSRC_PATH + "/format-indent-less.png")) self._action_indent_less = menu.addAction(icon, "&Unindent", self.unindent) diff --git a/requirements-doc.txt b/requirements-doc.txt index 70eb85bf..7205ef57 100644 --- a/requirements-doc.txt +++ b/requirements-doc.txt @@ -2,6 +2,7 @@ sphinx==7.2.6 sphinx-design==0.5.0 sphinx-copybutton==0.5.2 sphinx-tags==0.3.1 +sphinx-toolbox myst-parser==2.0.0 # FIXME: Using fork in order to enable the 'collapse_navbar=True' # option for the sphinx-theme. Upstream proposal: diff --git a/requirements.txt b/requirements.txt index 2b279db3..65380e93 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ # Build dependencies -setuptools==69.0.3 +setuptools==69.1.1 packaging==23.2 build==1.0.3 wheel==0.42.0 @@ -11,6 +11,3 @@ numpy==1.26.3; python_version >= '3.9' # For examples PyOpenGL - -# For tests -pyinstaller==3.6; platform_machine != 'aarch64' diff --git a/sources/pyside-tools/CMakeLists.txt b/sources/pyside-tools/CMakeLists.txt index d8a35330..e629ec57 100644 --- a/sources/pyside-tools/CMakeLists.txt +++ b/sources/pyside-tools/CMakeLists.txt @@ -44,7 +44,10 @@ else() "${TOOLS_PATH}/lupdate${CMAKE_EXECUTABLE_SUFFIX}" "${TOOLS_PATH}/qmllint${CMAKE_EXECUTABLE_SUFFIX}" "${TOOLS_PATH}/qmlformat${CMAKE_EXECUTABLE_SUFFIX}" - "${TOOLS_PATH}/qmlls${CMAKE_EXECUTABLE_SUFFIX}") + "${TOOLS_PATH}/qmlls${CMAKE_EXECUTABLE_SUFFIX}" + "${TOOLS_PATH}/qsb${CMAKE_EXECUTABLE_SUFFIX}" + "${TOOLS_PATH}/balsam${CMAKE_EXECUTABLE_SUFFIX}" + "${TOOLS_PATH}/balsamui${CMAKE_EXECUTABLE_SUFFIX}") if (APPLE) list(APPEND directories "${TOOLS_PATH}/Assistant.app" diff --git a/sources/pyside-tools/android_deploy.py b/sources/pyside-tools/android_deploy.py index 8ef59781..75269d62 100644 --- a/sources/pyside-tools/android_deploy.py +++ b/sources/pyside-tools/android_deploy.py @@ -8,8 +8,8 @@ import traceback from pathlib import Path from textwrap import dedent -from deploy_lib import (setup_python, create_config_file, cleanup, install_python_dependencies, - config_option_exists, MAJOR_VERSION) +from deploy_lib import (create_config_file, cleanup, config_option_exists, PythonExecutable, + MAJOR_VERSION, HELP_EXTRA_IGNORE_DIRS, HELP_EXTRA_MODULES) from deploy_lib.android import AndroidData, AndroidConfig from deploy_lib.android.buildozer import Buildozer @@ -46,26 +46,6 @@ from deploy_lib.android.buildozer import Buildozer Note: This file is used by both pyside6-deploy and pyside6-android-deploy """ -HELP_EXTRA_IGNORE_DIRS = dedent(""" - Comma separated directory names inside the project dir. These - directories will be skipped when searching for python files - relevant to the project. - - Example usage: --extra-ignore-dirs=doc,translations - """) - -HELP_EXTRA_MODULES = dedent(""" - Comma separated list of Qt modules to be added to the application, - in case they are not found automatically. - - This occurs when you have 'import PySide6' in your code instead - 'from PySide6 import '. The module name is specified - with either omitting the prefix of Qt or with it. - - Example usage 1: --extra-modules=Network,Svg - Example usage 2: --extra-modules=QtNetwork,QtSvg - """) - def main(name: str = None, pyside_wheel: Path = None, shiboken_wheel: Path = None, ndk_path: Path = None, sdk_path: Path = None, config_file: Path = None, init: bool = False, @@ -96,7 +76,7 @@ def main(name: str = None, pyside_wheel: Path = None, shiboken_wheel: Path = Non android_data = AndroidData(wheel_pyside=pyside_wheel, wheel_shiboken=shiboken_wheel, ndk_path=ndk_path, sdk_path=sdk_path) - python = setup_python(dry_run=dry_run, force=force, init=init) + python = PythonExecutable(dry_run=dry_run, init=init, force=force) config_file_exists = config_file and Path(config_file).exists() @@ -117,19 +97,19 @@ def main(name: str = None, pyside_wheel: Path = None, shiboken_wheel: Path = Non cleanup(config=config, is_android=True) - install_python_dependencies(config=config, python=python, init=init, - packages="android_packages", is_android=True) + python.install_dependencies(config=config, packages="android_packages", is_android=True) # set application name if name: config.title = name try: - config.modules += extra_modules + config.modules += list(set(extra_modules).difference(set(config.modules))) # this cannot be done when config file is initialized because cleanup() removes it # so this can only be done after the cleanup() config.find_and_set_jars_dir() + config.verify_and_set_recipe_dir() # TODO: include qml files from pysidedeploy.spec rather than from extensions # buildozer currently includes all the files with .qml extension diff --git a/sources/pyside-tools/android_deploy.pyproject b/sources/pyside-tools/android_deploy.pyproject index f976cb5a..bc634724 100644 --- a/sources/pyside-tools/android_deploy.pyproject +++ b/sources/pyside-tools/android_deploy.pyproject @@ -4,6 +4,6 @@ "deploy_lib/android/recipes/PySide6/__init__.tmpl.py", "deploy_lib/android/recipes/shiboken6/__init__.tmpl.py", "deploy_lib/android/__init__.py", "deploy_lib/android/android_helper.py", - "deploy_lib/android/buildozer.py" + "deploy_lib/android/buildozer.py", "deploy_lib/dependency_util.py" ] } diff --git a/sources/pyside-tools/cmake/PySideAndroid.cmake b/sources/pyside-tools/cmake/PySideAndroid.cmake index eafbdbbe..4b6260cc 100644 --- a/sources/pyside-tools/cmake/PySideAndroid.cmake +++ b/sources/pyside-tools/cmake/PySideAndroid.cmake @@ -16,11 +16,8 @@ macro(create_and_install_qt_javabindings) set(android_main_srcs "${QT6_INSTALL_PREFIX}/src/android/java/src/org/qtproject/qt/android/bindings") set(java_sources ${android_main_srcs}/QtActivity.java - ${android_main_srcs}/QtActivityLoader.java ${android_main_srcs}/QtApplication.java - ${android_main_srcs}/QtLoader.java ${android_main_srcs}/QtService.java - ${android_main_srcs}/QtServiceLoader.java ) # set android.jar from the sdk, for compiling the java files into .jar set(sdk_jar_location "${ANDROID_SDK_ROOT}/platforms/${ANDROID_PLATFORM}/android.jar") diff --git a/sources/pyside-tools/deploy.py b/sources/pyside-tools/deploy.py index 0aea807a..aa03d13d 100644 --- a/sources/pyside-tools/deploy.py +++ b/sources/pyside-tools/deploy.py @@ -34,14 +34,25 @@ import traceback from pathlib import Path from textwrap import dedent -from deploy_lib import (MAJOR_VERSION, Config, cleanup, config_option_exists, - finalize, create_config_file, install_python_dependencies, - setup_python) +from deploy_lib import (MAJOR_VERSION, DesktopConfig, cleanup, config_option_exists, + finalize, create_config_file, PythonExecutable, Nuitka, + HELP_EXTRA_MODULES, HELP_EXTRA_IGNORE_DIRS) + + +TOOL_DESCRIPTION = dedent(f""" + This tool deploys PySide{MAJOR_VERSION} to desktop (Windows, Linux, + macOS) platforms. The following types of executables are produced as per + the platform: + + Windows = .exe + macOS = .app + Linux = .bin + """) def main(main_file: Path = None, name: str = None, config_file: Path = None, init: bool = False, loglevel=logging.WARNING, dry_run: bool = False, keep_deployment_files: bool = False, - force: bool = False): + force: bool = False, extra_ignore_dirs: str = None, extra_modules_grouped: str = None): logging.basicConfig(level=loglevel) if config_file and not config_file.exists() and not main_file.exists(): @@ -57,7 +68,19 @@ def main(main_file: Path = None, name: str = None, config_file: Path = None, ini config = None logging.info("[DEPLOY] Start") - python = setup_python(dry_run=dry_run, force=force, init=init) + if extra_ignore_dirs: + extra_ignore_dirs = extra_ignore_dirs.split(",") + + extra_modules = [] + if extra_modules_grouped: + tmp_extra_modules = extra_modules_grouped.split(",") + for extra_module in tmp_extra_modules: + if extra_module.startswith("Qt"): + extra_modules.append(extra_module[2:]) + else: + extra_modules.append(extra_module) + + python = PythonExecutable(dry_run=dry_run, init=init, force=force) config_file_exists = config_file and Path(config_file).exists() if config_file_exists: @@ -66,8 +89,9 @@ def main(main_file: Path = None, name: str = None, config_file: Path = None, ini config_file = create_config_file(dry_run=dry_run, config_file=config_file, main_file=main_file) - config = Config(config_file=config_file, source_file=main_file, python_exe=python.exe, - dry_run=dry_run, existing_config_file=config_file_exists) + config = DesktopConfig(config_file=config_file, source_file=main_file, python_exe=python.exe, + dry_run=dry_run, existing_config_file=config_file_exists, + extra_ignore_dirs=extra_ignore_dirs) # set application name if name: @@ -75,20 +99,24 @@ def main(main_file: Path = None, name: str = None, config_file: Path = None, ini cleanup(config=config) - install_python_dependencies(config=config, python=python, init=init, - packages="packages") + python.install_dependencies(config=config, packages="packages") # required by Nuitka for pyenv Python add_arg = " --static-libpython=no" if python.is_pyenv_python() and add_arg not in config.extra_args: config.extra_args += add_arg + config.modules += list(set(extra_modules).difference(set(config.modules))) + # writing config file # in the case of --dry-run, we use default.spec as reference. Do not save the changes # for --dry-run if not dry_run: config.update_config() + if config.qml_files: + logging.info(f"[DEPLOY] Included QML files: {config.qml_files}") + if init: # config file created above. Exiting. logging.info(f"[DEPLOY]: Config file {config.config_file} created") @@ -99,9 +127,15 @@ def main(main_file: Path = None, name: str = None, config_file: Path = None, ini if not dry_run: logging.info("[DEPLOY] Deploying application") - command_str = python.create_executable(source_file=config.source_file, + nuitka = Nuitka(nuitka=[python.exe, "-m", "nuitka"]) + command_str = nuitka.create_executable(source_file=config.source_file, extra_args=config.extra_args, - config=config) + qml_files=config.qml_files, + qt_plugins=config.qt_plugins, + excluded_qml_plugins=config.excluded_qml_plugins, + icon=config.icon, + dry_run=dry_run, + permissions=config.permissions) except Exception: print(f"[DEPLOY] Exception occurred: {traceback.format_exc()}") finally: @@ -115,11 +149,7 @@ def main(main_file: Path = None, name: str = None, config_file: Path = None, ini if __name__ == "__main__": - parser = argparse.ArgumentParser( - description=(f"This tool deploys PySide{MAJOR_VERSION} to desktop (Windows, Linux, macOS)" - "platforms"), - formatter_class=argparse.RawTextHelpFormatter, - ) + parser = argparse.ArgumentParser(description=TOOL_DESCRIPTION) parser.add_argument("-c", "--config-file", type=lambda p: Path(p).absolute(), default=(Path.cwd() / "pysidedeploy.spec"), @@ -148,7 +178,11 @@ if __name__ == "__main__": parser.add_argument("--name", type=str, help="Application name") + parser.add_argument("--extra-ignore-dirs", type=str, help=HELP_EXTRA_IGNORE_DIRS) + + parser.add_argument("--extra-modules", type=str, help=HELP_EXTRA_MODULES) + args = parser.parse_args() main(args.main_file, args.name, args.config_file, args.init, args.loglevel, args.dry_run, - args.keep_deployment_files, args.force) + args.keep_deployment_files, args.force, args.extra_ignore_dirs, args.extra_modules) diff --git a/sources/pyside-tools/deploy.pyproject b/sources/pyside-tools/deploy.pyproject index eef1668c..0e6ca825 100644 --- a/sources/pyside-tools/deploy.pyproject +++ b/sources/pyside-tools/deploy.pyproject @@ -2,6 +2,7 @@ "files": ["deploy.py", "deploy_lib/__init__.py", "deploy_lib/commands.py", "deploy_lib/config.py", "deploy_lib/default.spec", "deploy_lib/nuitka_helper.py", "deploy_lib/pyside_icon.ico", "deploy_lib/pyside_icon.icns","deploy_lib/pyside_icon.jpg", - "deploy_lib/python_helper.py", "deploy_lib/deploy_util.py" + "deploy_lib/python_helper.py", "deploy_lib/deploy_util.py", + "deploy_lib/dependency_util.py" ] } diff --git a/sources/pyside-tools/deploy_lib/__init__.py b/sources/pyside-tools/deploy_lib/__init__.py index 8c5d8b4e..a40d0838 100644 --- a/sources/pyside-tools/deploy_lib/__init__.py +++ b/sources/pyside-tools/deploy_lib/__init__.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only import sys from pathlib import Path +from textwrap import dedent MAJOR_VERSION = 6 @@ -10,16 +11,49 @@ if sys.platform == "win32": EXE_FORMAT = ".exe" elif sys.platform == "darwin": IMAGE_FORMAT = ".icns" - EXE_FORMAT = ".bin" + EXE_FORMAT = ".app" else: IMAGE_FORMAT = ".jpg" EXE_FORMAT = ".bin" DEFAULT_APP_ICON = str((Path(__file__).parent / f"pyside_icon{IMAGE_FORMAT}").resolve()) +IMPORT_WARNING_PYSIDE = (f"[DEPLOY] Found 'import PySide6' in file {0}" + ". Use 'from PySide6 import ' or pass the module" + " needed using --extra-modules command line argument") +HELP_EXTRA_IGNORE_DIRS = dedent(""" + Comma-separated directory names inside the project dir. These + directories will be skipped when searching for Python files + relevant to the project. + + Example usage: --extra-ignore-dirs=doc,translations + """) + +HELP_EXTRA_MODULES = dedent(""" + Comma-separated list of Qt modules to be added to the application, + in case they are not found automatically. + + This occurs when you have 'import PySide6' in your code instead + 'from PySide6 import '. The module name is specified + by either omitting the prefix of Qt or including it. + + Example usage 1: --extra-modules=Network,Svg + Example usage 2: --extra-modules=QtNetwork,QtSvg + """) + + +def get_all_pyside_modules(): + """ + Returns all the modules installed with PySide6 + """ + import PySide6 + # They all start with `Qt` as the prefix. Removing this prefix and getting the actual + # module name + return [module[2:] for module in PySide6.__all__] + -from .commands import run_command +from .commands import run_command, run_qmlimportscanner +from .dependency_util import find_pyside_modules, find_permission_categories, QtDependencyReader from .nuitka_helper import Nuitka -from .python_helper import PythonExecutable, find_pyside_modules -from .config import BaseConfig, Config -from .deploy_util import (cleanup, finalize, create_config_file, setup_python, - install_python_dependencies, config_option_exists) +from .config import BaseConfig, Config, DesktopConfig +from .python_helper import PythonExecutable +from .deploy_util import cleanup, finalize, create_config_file, config_option_exists diff --git a/sources/pyside-tools/deploy_lib/android/__init__.py b/sources/pyside-tools/deploy_lib/android/__init__.py index 59cd510c..c3027762 100644 --- a/sources/pyside-tools/deploy_lib/android/__init__.py +++ b/sources/pyside-tools/deploy_lib/android/__init__.py @@ -1,7 +1,16 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -from .android_helper import (create_recipe, extract_and_copy_jar, - get_wheel_android_arch, AndroidData, get_llvm_readobj, - find_lib_dependencies, find_qtlibs_in_wheel) +# maps instruction set to Android platform names +platform_map = {"aarch64": "arm64-v8a", + "armv7a": "armeabi-v7a", + "i686": "x86", + "x86_64": "x86_64", + "arm64-v8a": "arm64-v8a", + "armeabi-v7a": "armeabi-v7a", + "x86": "x86"} + +from .android_helper import (create_recipe, extract_and_copy_jar, get_wheel_android_arch, + AndroidData, get_llvm_readobj, find_lib_dependencies, + find_qtlibs_in_wheel) from .android_config import AndroidConfig diff --git a/sources/pyside-tools/deploy_lib/android/android_config.py b/sources/pyside-tools/deploy_lib/android/android_config.py index 442672a2..ad818c2f 100644 --- a/sources/pyside-tools/deploy_lib/android/android_config.py +++ b/sources/pyside-tools/deploy_lib/android/android_config.py @@ -1,14 +1,20 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +import re +import tempfile import logging +import zipfile +import xml.etree.ElementTree as ET from typing import List from pathlib import Path +from pkginfo import Wheel -from . import extract_and_copy_jar, get_wheel_android_arch -from .. import Config, find_pyside_modules +from . import (extract_and_copy_jar, get_wheel_android_arch, find_lib_dependencies, + get_llvm_readobj, find_qtlibs_in_wheel, platform_map, create_recipe) +from .. import (Config, find_pyside_modules, get_all_pyside_modules, MAJOR_VERSION) -ANDROID_NDK_VERSION = "25c" +ANDROID_NDK_VERSION = "26b" ANDROID_DEPLOY_CACHE = Path.home() / ".pyside6_android_deploy" @@ -86,28 +92,52 @@ class AndroidConfig(Config): if jars_dir_temp and Path(jars_dir_temp).resolve().exists(): self.jars_dir = Path(jars_dir_temp).resolve() - self._modules = [] - if self.get_value("buildozer", "modules"): - self.modules = self.get_value("buildozer", "modules").split(",") - else: - self._find_and_set_pysidemodules() - self._find_and_set_qtquick_modules() - self._arch = None if self.get_value("buildozer", "arch"): self.arch = self.get_value("buildozer", "arch") else: self._find_and_set_arch() + # maps to correct platform name incase the instruction set was specified + self._arch = platform_map[self.arch] + + self._mode = self.get_value("buildozer", "mode") + + self.qt_libs_path: zipfile.Path = find_qtlibs_in_wheel(wheel_pyside=self.wheel_pyside) + logging.info(f"[DEPLOY] Qt libs path inside wheel: {str(self.qt_libs_path)}") + + if self.get_value("qt", "modules"): + self.modules = self.get_value("qt", "modules").split(",") + else: + self._find_and_set_pysidemodules() + self._find_and_set_qtquick_modules() + self.modules += self._find_dependent_qt_modules() + # remove duplicates + self.modules = list(set(self.modules)) + + # gets the xml dependency files from Qt installation path + self._dependency_files = [] + self._find_and_set_dependency_files() + + dependent_plugins = [] self._local_libs = [] if self.get_value("buildozer", "local_libs"): - self.local_libs = self.get_value("buildozer", "local_libs").split(",") + self._local_libs = self.get_value("buildozer", "local_libs").split(",") + else: + # the local_libs can also store dependent plugins + local_libs, dependent_plugins = self._find_local_libs() + self.local_libs = list(set(local_libs)) self._qt_plugins = [] if self.get_value("android", "plugins"): self._qt_plugins = self.get_value("android", "plugins").split(",") + elif dependent_plugins: + self._find_plugin_dependencies(dependent_plugins) + self.qt_plugins = list(set(dependent_plugins)) - self._mode = self.get_value("buildozer", "mode") + recipe_dir_temp = self.get_value("buildozer", "recipe_dir") + if recipe_dir_temp: + self.recipe_dir = Path(recipe_dir_temp) @property def qt_plugins(self): @@ -158,7 +188,7 @@ class AndroidConfig(Config): @modules.setter def modules(self, modules): self._modules = modules - self.set_value("buildozer", "modules", ",".join(modules)) + self.set_value("qt", "modules", ",".join(modules)) @property def local_libs(self): @@ -218,6 +248,14 @@ class AndroidConfig(Config): if self._wheel_shiboken: self.set_value("android", "wheel_shiboken", str(self._wheel_shiboken)) + @property + def dependency_files(self): + return self._dependency_files + + @dependency_files.setter + def dependency_files(self, dependency_files): + self._dependency_files = dependency_files + def _find_and_set_pysidemodules(self): self.modules = find_pyside_modules(project_dir=self.project_dir, extra_ignore_dirs=self.extra_ignore_dirs, @@ -242,15 +280,167 @@ class AndroidConfig(Config): raise RuntimeError("[DEPLOY] PySide wheel corrupted. Wheel name should end with" "platform name") - def _find_and_set_qtquick_modules(self): - """Identify if QtQuick is used in QML files and add them as dependency + def _find_dependent_qt_modules(self): """ - extra_modules = [] - - if "QtQuick" in self.qml_modules: - extra_modules.append("Quick") - - if "QtQuick.Controls" in self.qml_modules: - extra_modules.append("QuickControls2") - - self.modules += extra_modules + Given pysidedeploy_config.modules, find all the other dependent Qt modules. This is + done by using llvm-readobj (readelf) to find the dependent libraries from the module + library. + """ + dependent_modules = set() + all_dependencies = set() + lib_pattern = re.compile(f"libQt6(?P.*)_{self.arch}") + + llvm_readobj = get_llvm_readobj(self.ndk_path) + if not llvm_readobj.exists(): + raise FileNotFoundError(f"[DEPLOY] {llvm_readobj} does not exist." + "Finding Qt dependencies failed") + + archive = zipfile.ZipFile(self.wheel_pyside) + lib_path_suffix = Path(str(self.qt_libs_path)).relative_to(self.wheel_pyside) + + with tempfile.TemporaryDirectory() as tmpdir: + archive.extractall(tmpdir) + qt_libs_tmpdir = Path(tmpdir) / lib_path_suffix + # find the lib folder where Qt libraries are stored + for module_name in sorted(self.modules): + qt_module_path = qt_libs_tmpdir / f"libQt6{module_name}_{self.arch}.so" + if not qt_module_path.exists(): + raise FileNotFoundError(f"[DEPLOY] libQt6{module_name}_{self.arch}.so not found" + " inside the wheel") + find_lib_dependencies(llvm_readobj=llvm_readobj, lib_path=qt_module_path, + dry_run=self.dry_run, + used_dependencies=all_dependencies) + + for dependency in all_dependencies: + match = lib_pattern.search(dependency) + if match: + module = match.group("mod_name") + if module not in self.modules: + dependent_modules.add(module) + + # check if the PySide6 binary for the Qt module actually exists + # eg: libQt6QmlModels.so exists and it includes QML types. Hence, it makes no + dependent_modules = [module for module in dependent_modules if module in + get_all_pyside_modules()] + dependent_modules_str = ",".join(dependent_modules) + logging.info("[DEPLOY] The following extra dependencies were found:" + f" {dependent_modules_str}") + + return dependent_modules + + def _find_and_set_dependency_files(self) -> List[zipfile.Path]: + """ + Based on `modules`, returns the Qt6{module}_{arch}-android-dependencies.xml file, which + contains the various dependencies of the module, like permissions, plugins etc + """ + needed_dependency_files = [(f"Qt{MAJOR_VERSION}{module}_{self.arch}" + "-android-dependencies.xml") for module in self.modules] + + for dependency_file_name in needed_dependency_files: + dependency_file = self.qt_libs_path / dependency_file_name + if dependency_file.exists(): + self._dependency_files.append(dependency_file) + + logging.info("[DEPLOY] The following dependency files were found: " + f"{*self._dependency_files,}") + + def _find_local_libs(self): + local_libs = set() + plugins = set() + lib_pattern = re.compile(f"lib(?P.*)_{self.arch}") + for dependency_file in self._dependency_files: + xml_content = dependency_file.read_text() + root = ET.fromstring(xml_content) + for local_lib in root.iter("lib"): + + if 'file' not in local_lib.attrib: + if 'name' not in local_lib.attrib: + logging.warning("[DEPLOY] Invalid android dependency file" + f" {str(dependency_file)}") + continue + + file = local_lib.attrib['file'] + if file.endswith(".so"): + # file_name starts with lib and ends with the platform name + # eg: lib_x86_64.so + file_name = Path(file).stem + + # we only need lib_name, because lib and arch gets re-added by + # python-for-android + match = lib_pattern.search(file_name) + if match: + lib_name = match.group("lib_name") + local_libs.add(lib_name) + if lib_name.startswith("plugins"): + plugin_name = lib_name.split('plugins_', 1)[1] + plugins.add(plugin_name) + + return list(local_libs), list(plugins) + + def _find_plugin_dependencies(self, dependent_plugins: List[str]): + # The `bundled` element in the dependency xml files points to the folder where + # additional dependencies for the application exists. Inspecting the depenency files + # in android, this always points to the specific Qt plugin dependency folder. + # eg: for application using Qt Multimedia, this looks like: + # + # The code recusively checks all these dependent folders and adds the necessary plugins + # as dependencies + lib_pattern = re.compile(f"libplugins_(?P.*)_{self.arch}.so") + for dependency_file in self._dependency_files: + xml_content = dependency_file.read_text() + root = ET.fromstring(xml_content) + for bundled_element in root.iter("bundled"): + # the attribute 'file' can be misleading, but it always points to the plugin + # folder on inspecting the dependency files + if 'file' not in bundled_element.attrib: + logging.warning("[DEPLOY] Invalid Android dependency file" + f" {str(dependency_file)}") + continue + + # from "./plugins/multimedia" to absolute path in wheel + plugin_module_folder = bundled_element.attrib['file'] + # they all should start with `./plugins` + if plugin_module_folder.startswith("./plugins"): + plugin_module_folder = plugin_module_folder.partition("./plugins/")[2] + else: + continue + + absolute_plugin_module_folder = (self.qt_libs_path.parent / "plugins" + / plugin_module_folder) + + if not absolute_plugin_module_folder.is_dir(): + logging.warning(f"[DEPLOY] Qt plugin folder '{plugin_module_folder}' does not" + " exist or is not a directory for this Android platform") + continue + + for plugin in absolute_plugin_module_folder.iterdir(): + plugin_name = plugin.name + if plugin_name.endswith(".so") and plugin_name.startswith("libplugins"): + # we only need part of plugin_name, because `lib` prefix and `arch` suffix + # gets re-added by python-for-android + match = lib_pattern.search(plugin_name) + if match: + plugin_infix_name = match.group("plugin_name") + if plugin_infix_name not in dependent_plugins: + dependent_plugins.append(plugin_infix_name) + + def verify_and_set_recipe_dir(self): + # create recipes + # https://python-for-android.readthedocs.io/en/latest/recipes/ + # These recipes are manually added through buildozer.spec file to be used by + # python_for_android while building the distribution + + if not self.recipes_exist() and not self.dry_run: + logging.info("[DEPLOY] Creating p4a recipes for PySide6 and shiboken6") + version = Wheel(self.wheel_pyside).version + create_recipe(version=version, component=f"PySide{MAJOR_VERSION}", + wheel_path=self.wheel_pyside, + generated_files_path=self.generated_files_path, + qt_modules=self.modules, + local_libs=self.local_libs, + plugins=self.qt_plugins) + create_recipe(version=version, component=f"shiboken{MAJOR_VERSION}", + wheel_path=self.wheel_shiboken, + generated_files_path=self.generated_files_path) + self.recipe_dir = ((self.generated_files_path + / "recipes").resolve()) diff --git a/sources/pyside-tools/deploy_lib/android/android_helper.py b/sources/pyside-tools/deploy_lib/android/android_helper.py index 23034364..7d2f5d57 100644 --- a/sources/pyside-tools/deploy_lib/android/android_helper.py +++ b/sources/pyside-tools/deploy_lib/android/android_helper.py @@ -102,8 +102,15 @@ def find_lib_dependencies(llvm_readobj: Path, lib_path: Path, used_dependencies: if lib_path.name in used_dependencies: return + used_dependencies.add(lib_path.name) + command = [str(llvm_readobj), "--needed-libs", str(lib_path)] - _, output = run_command(command=command, dry_run=dry_run, fetch_output=True) + + # even if dry_run is given, we need to run the actual command to see all the dependencies + # for which llvm-readelf is run. + if dry_run: + _, output = run_command(command=command, dry_run=dry_run, fetch_output=True) + _, output = run_command(command=command, dry_run=False, fetch_output=True) dependencies = set() neededlibraries_found = False diff --git a/sources/pyside-tools/deploy_lib/android/buildozer.py b/sources/pyside-tools/deploy_lib/android/buildozer.py index 3c188b1c..0c314c35 100644 --- a/sources/pyside-tools/deploy_lib/android/buildozer.py +++ b/sources/pyside-tools/deploy_lib/android/buildozer.py @@ -3,27 +3,17 @@ import sys import logging -import re -import tempfile import xml.etree.ElementTree as ET import zipfile -import PySide6 from pathlib import Path from typing import List -from pkginfo import Wheel - -from .. import MAJOR_VERSION, BaseConfig, Config, run_command -from . import (create_recipe, find_lib_dependencies, find_qtlibs_in_wheel, - get_llvm_readobj) - -# They all start with `Qt` as the prefix. Removing this prefix and getting the actual -# module name -ALL_PYSIDE_MODULES = [module[2:] for module in PySide6.__all__] +from . import AndroidConfig +from .. import BaseConfig, run_command class BuildozerConfig(BaseConfig): - def __init__(self, buildozer_spec_file: Path, pysidedeploy_config: Config): + def __init__(self, buildozer_spec_file: Path, pysidedeploy_config: AndroidConfig): super().__init__(buildozer_spec_file, comment_prefixes="#") self.set_value("app", "title", pysidedeploy_config.title) self.set_value("app", "package.name", pysidedeploy_config.title) @@ -43,70 +33,33 @@ class BuildozerConfig(BaseConfig): if pysidedeploy_config.sdk_path: self.set_value("app", "android.sdk_path", str(pysidedeploy_config.sdk_path)) - platform_map = {"aarch64": "arm64-v8a", - "armv7a": "armeabi-v7a", - "i686": "x86", - "x86_64": "x86_64"} - self.arch = platform_map[pysidedeploy_config.arch] - self.set_value("app", "android.archs", self.arch) + self.set_value("app", "android.archs", pysidedeploy_config.arch) # p4a changes self.set_value("app", "p4a.bootstrap", "qt") - - self.qt_libs_path: zipfile.Path = ( - find_qtlibs_in_wheel(wheel_pyside=pysidedeploy_config.wheel_pyside)) - logging.info(f"[DEPLOY] Found Qt libs path inside wheel: {str(self.qt_libs_path)}") - - extra_modules = self.__find_dependent_qt_modules(pysidedeploy_config) - logging.info(f"[DEPLOY] Other dependent modules to be added: {extra_modules}") - pysidedeploy_config.modules = pysidedeploy_config.modules + extra_modules - modules = ",".join(pysidedeploy_config.modules) - - # gets the xml dependency files from Qt installation path - dependency_files = self.__get_dependency_files(modules=pysidedeploy_config.modules, - arch=self.arch) - - dependent_plugins = [] - # the local_libs can also store dependent plugins - local_libs, dependent_plugins = self.__find_local_libs(dependency_files) - pysidedeploy_config.local_libs += local_libs - - self.__find_plugin_dependencies(dependency_files, dependent_plugins) - pysidedeploy_config.qt_plugins += dependent_plugins - - local_libs = ",".join(pysidedeploy_config.local_libs) - - # create recipes - # https://python-for-android.readthedocs.io/en/latest/recipes/ - # These recipes are manually added through buildozer.spec file to be used by - # python_for_android while building the distribution - if not pysidedeploy_config.recipes_exist() and not pysidedeploy_config.dry_run: - logging.info("[DEPLOY] Creating p4a recipes for PySide6 and shiboken6") - version = Wheel(pysidedeploy_config.wheel_pyside).version - create_recipe(version=version, component=f"PySide{MAJOR_VERSION}", - wheel_path=pysidedeploy_config.wheel_pyside, - generated_files_path=pysidedeploy_config.generated_files_path, - qt_modules=pysidedeploy_config.modules, - local_libs=pysidedeploy_config.local_libs, - plugins=pysidedeploy_config.qt_plugins) - create_recipe(version=version, component=f"shiboken{MAJOR_VERSION}", - wheel_path=pysidedeploy_config.wheel_shiboken, - generated_files_path=pysidedeploy_config.generated_files_path) - pysidedeploy_config.recipe_dir = ((pysidedeploy_config.generated_files_path - / "recipes").resolve()) self.set_value('app', "p4a.local_recipes", str(pysidedeploy_config.recipe_dir)) + # add p4a branch + # by default the master branch is used + # https://github.com/kivy/python-for-android/commit/b92522fab879dbfc0028966ca3c59ef46ab7767d + # has not been merged to master yet. So, we use the develop branch for now + # TODO: remove this once the above commit is merged to master + self.set_value("app", "p4a.branch", "develop") + # add permissions - permissions = self.__find_permissions(dependency_files) + permissions = self.__find_permissions(pysidedeploy_config.dependency_files) permissions = ", ".join(permissions) self.set_value("app", "android.permissions", permissions) # add jars and initClasses for the jars - jars, init_classes = self.__find_jars(dependency_files, pysidedeploy_config.jars_dir) + jars, init_classes = self.__find_jars(pysidedeploy_config.dependency_files, + pysidedeploy_config.jars_dir) self.set_value("app", "android.add_jars", ",".join(jars)) - init_classes = ",".join(init_classes) # extra arguments specific to Qt + modules = ",".join(pysidedeploy_config.modules) + local_libs = ",".join(pysidedeploy_config.local_libs) + init_classes = ",".join(init_classes) extra_args = (f"--qt-libs={modules} --load-local-libs={local_libs}" f" --init-classes={init_classes}") self.set_value("app", "p4a.extra_args", extra_args) @@ -123,25 +76,6 @@ class BuildozerConfig(BaseConfig): self.update_config() - def __get_dependency_files(self, modules: List[str], arch: str) -> List[zipfile.Path]: - """ - Based on pysidedeploy_config.modules, returns the - Qt6{module}_{arch}-android-dependencies.xml file, which contains the various - dependencies of the module, like permissions, plugins etc - """ - dependency_files = [] - needed_dependency_files = [(f"Qt{MAJOR_VERSION}{module}_{arch}" - "-android-dependencies.xml") for module in modules] - - for dependency_file_name in needed_dependency_files: - dependency_file = self.qt_libs_path / dependency_file_name - if dependency_file.exists(): - dependency_files.append(dependency_file) - - logging.info(f"[DEPLOY] The following dependency files were found: {*dependency_files,}") - - return dependency_files - def __find_permissions(self, dependency_files: List[zipfile.Path]): permissions = set() for dependency_file in dependency_files: @@ -186,145 +120,12 @@ class BuildozerConfig(BaseConfig): return jars, init_classes - def __find_local_libs(self, dependency_files: List[zipfile.Path]): - local_libs = set() - plugins = set() - lib_pattern = re.compile(f"lib(?P.*)_{self.arch}") - for dependency_file in dependency_files: - xml_content = dependency_file.read_text() - root = ET.fromstring(xml_content) - for local_lib in root.iter("lib"): - - if 'file' not in local_lib.attrib: - if 'name' not in local_lib.attrib: - logging.warning("[DEPLOY] Invalid android dependency file" - f" {str(dependency_file)}") - continue - - file = local_lib.attrib['file'] - if file.endswith(".so"): - # file_name starts with lib and ends with the platform name - # eg: lib_x86_64.so - file_name = Path(file).stem - - if file_name.startswith("libplugins_platforms_qtforandroid"): - # the platform library is a requisite and is already added from the - # configuration file - continue - - # we only need lib_name, because lib and arch gets re-added by - # python-for-android - match = lib_pattern.search(file_name) - if match: - lib_name = match.group("lib_name") - local_libs.add(lib_name) - if lib_name.startswith("plugins"): - plugin_name = lib_name.split('plugins_', 1)[1] - plugins.add(plugin_name) - - return list(local_libs), list(plugins) - - def __find_plugin_dependencies(self, dependency_files: List[zipfile.Path], - dependent_plugins: List[str]): - # The `bundled` element in the dependency xml files points to the folder where - # additional dependencies for the application exists. Inspecting the depenency files - # in android, this always points to the specific Qt plugin dependency folder. - # eg: for application using Qt Multimedia, this looks like: - # - # The code recusively checks all these dependent folders and adds the necessary plugins - # as dependencies - lib_pattern = re.compile(f"libplugins_(?P.*)_{self.arch}.so") - for dependency_file in dependency_files: - xml_content = dependency_file.read_text() - root = ET.fromstring(xml_content) - for bundled_element in root.iter("bundled"): - # the attribute 'file' can be misleading, but it always points to the plugin - # folder on inspecting the dependency files - if 'file' not in bundled_element.attrib: - logging.warning("[DEPLOY] Invalid Android dependency file" - f" {str(dependency_file)}") - continue - - # from "./plugins/multimedia" to absolute path in wheel - plugin_module_folder = bundled_element.attrib['file'] - # they all should start with `./plugins` - if plugin_module_folder.startswith("./plugins"): - plugin_module_folder = plugin_module_folder.partition("./plugins/")[2] - else: - continue - - absolute_plugin_module_folder = (self.qt_libs_path.parent / "plugins" - / plugin_module_folder) - - if not absolute_plugin_module_folder.is_dir(): - logging.warning(f"[DEPLOY] Qt plugin folder '{plugin_module_folder}' does not" - " exist or is not a directory for this Android platform") - continue - - for plugin in absolute_plugin_module_folder.iterdir(): - plugin_name = plugin.name - if plugin_name.endswith(".so") and plugin_name.startswith("libplugins"): - # we only need part of plugin_name, because `lib` prefix and `arch` suffix - # gets re-added by python-for-android - match = lib_pattern.search(plugin_name) - if match: - plugin_infix_name = match.group("plugin_name") - if plugin_infix_name not in dependent_plugins: - dependent_plugins.append(plugin_infix_name) - - def __find_dependent_qt_modules(self, pysidedeploy_config: Config): - """ - Given pysidedeploy_config.modules, find all the other dependent Qt modules. This is - done by using llvm-readobj (readelf) to find the dependent libraries from the module - library. - """ - dependent_modules = set() - all_dependencies = set() - lib_pattern = re.compile(f"libQt6(?P.*)_{self.arch}") - - llvm_readobj = get_llvm_readobj(pysidedeploy_config.ndk_path) - if not llvm_readobj.exists(): - raise FileNotFoundError(f"[DEPLOY] {llvm_readobj} does not exist." - "Finding Qt dependencies failed") - - archive = zipfile.ZipFile(pysidedeploy_config.wheel_pyside) - lib_path_suffix = Path(str(self.qt_libs_path)).relative_to(pysidedeploy_config.wheel_pyside) - - with tempfile.TemporaryDirectory() as tmpdir: - archive.extractall(tmpdir) - qt_libs_tmpdir = Path(tmpdir) / lib_path_suffix - # find the lib folder where Qt libraries are stored - for module_name in pysidedeploy_config.modules: - qt_module_path = qt_libs_tmpdir / f"libQt6{module_name}_{self.arch}.so" - if not qt_module_path.exists(): - raise FileNotFoundError(f"[DEPLOY] libQt6{module_name}_{self.arch}.so not found" - " inside the wheel") - find_lib_dependencies(llvm_readobj=llvm_readobj, lib_path=qt_module_path, - dry_run=pysidedeploy_config.dry_run, - used_dependencies=all_dependencies) - - for dependency in all_dependencies: - match = lib_pattern.search(dependency) - if match: - module = match.group("mod_name") - if module not in pysidedeploy_config.modules: - dependent_modules.add(module) - - # check if the PySide6 binary for the Qt module actually exists - # eg: libQt6QmlModels.so exists and it includes QML types. Hence, it makes no - dependent_modules = [module for module in dependent_modules if module in ALL_PYSIDE_MODULES] - dependent_modules_str = ",".join(dependent_modules) - logging.info("[DEPLOY] The following extra dependencies were found:" - f" {dependent_modules_str}") - - return list(dependent_modules) - class Buildozer: dry_run = False @staticmethod - def initialize(pysidedeploy_config: Config): + def initialize(pysidedeploy_config: AndroidConfig): project_dir = Path(pysidedeploy_config.project_dir) buildozer_spec = project_dir / "buildozer.spec" if buildozer_spec.exists(): diff --git a/sources/pyside-tools/deploy_lib/config.py b/sources/pyside-tools/deploy_lib/config.py index 5dbaa68e..d59dd92a 100644 --- a/sources/pyside-tools/deploy_lib/config.py +++ b/sources/pyside-tools/deploy_lib/config.py @@ -1,29 +1,42 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +import sys import configparser import logging import warnings from configparser import ConfigParser +from typing import List from pathlib import Path from project import ProjectData - -from .commands import run_qmlimportscanner -from . import DEFAULT_APP_ICON +from . import (DEFAULT_APP_ICON, find_pyside_modules, find_permission_categories, + QtDependencyReader, run_qmlimportscanner) # Some QML plugins like QtCore are excluded from this list as they don't contribute much to # executable size. Excluding them saves the extra processing of checking for them in files EXCLUDED_QML_PLUGINS = {"QtQuick", "QtQuick3D", "QtCharts", "QtWebEngine", "QtTest", "QtSensors"} +PERMISSION_MAP = {"Bluetooth": "NSBluetoothAlwaysUsageDescription:BluetoothAccess", + "Camera": "NSCameraUsageDescription:CameraAccess", + "Microphone": "NSMicrophoneUsageDescription:MicrophoneAccess", + "Contacts": "NSContactsUsageDescription:ContactsAccess", + "Calendar": "NSCalendarsUsageDescription:CalendarAccess", + # for iOS NSLocationWhenInUseUsageDescription and + # NSLocationAlwaysAndWhenInUseUsageDescription are also required. + "Location": "NSLocationUsageDescription:LocationAccess", + } -class BaseConfig: +class BaseConfig: + """Wrapper class around any .spec file with function to read and set values for the .spec file + """ def __init__(self, config_file: Path, comment_prefixes: str = "/", existing_config_file: bool = False) -> None: self.config_file = config_file self.existing_config_file = existing_config_file - self.parser = ConfigParser(comment_prefixes=comment_prefixes, allow_no_value=True) + self.parser = ConfigParser(comment_prefixes=comment_prefixes, strict=False, + allow_no_value=True) self.parser.read(self.config_file) def update_config(self): @@ -61,9 +74,10 @@ class Config(BaseConfig): """ def __init__(self, config_file: Path, source_file: Path, python_exe: Path, dry_run: bool, - existing_config_file: bool = False): + existing_config_file: bool = False, extra_ignore_dirs: List[str] = None): super().__init__(config_file=config_file, existing_config_file=existing_config_file) + self.extra_ignore_dirs = extra_ignore_dirs self._dry_run = dry_run self.qml_modules = set() # set source_file @@ -123,6 +137,8 @@ class Config(BaseConfig): self._generated_files_path = self.project_dir / "deployment" + self.modules = [] + def set_or_fetch(self, config_property_val, config_property_key, config_property_group="app"): """ Write to config_file if 'config_property_key' is known without config_file @@ -222,6 +238,15 @@ class Config(BaseConfig): def exe_dir(self, exe_dir: Path): self._exe_dir = exe_dir + @property + def modules(self): + return self._modules + + @modules.setter + def modules(self, modules): + self._modules = modules + self.set_value("qt", "modules", ",".join(modules)) + def _find_and_set_qml_files(self): """Fetches all the qml_files in the folder and sets them if the field qml_files is empty in the config_dir""" @@ -233,27 +258,17 @@ class Config(BaseConfig): self.qml_files = qml_files else: qml_files_temp = None - source_file = ( - Path(self.get_value("app", "input_file")) - if self.get_value("app", "input_file") - else None - ) - python_exe = ( - Path(self.get_value("python", "python_path")) - if self.get_value("python", "python_path") - else None - ) - if source_file and python_exe: + if self.source_file and self.python_path: if not self.qml_files: - qml_files_temp = list(source_file.parent.glob("**/*.qml")) + qml_files_temp = list(self.source_file.parent.glob("**/*.qml")) # add all QML files, excluding the ones shipped with installed PySide6 # The QML files shipped with PySide6 gets added if venv is used, # because of recursive glob - if python_exe.parent.parent == source_file.parent: + if self.python_path.parent.parent == self.source_file.parent: # python venv path is inside the main source dir qml_files_temp = list( - set(qml_files_temp) - set(python_exe.parent.parent.rglob("*.qml")) + set(qml_files_temp) - set(self.python_path.parent.parent.rglob("*.qml")) ) if len(qml_files_temp) > 500: @@ -332,3 +347,113 @@ class Config(BaseConfig): config_property_val=self.exe_dir, config_property_key="exec_directory" ) ).absolute() + + def _find_and_set_pysidemodules(self): + self.modules = find_pyside_modules(project_dir=self.project_dir, + extra_ignore_dirs=self.extra_ignore_dirs, + project_data=self.project_data) + logging.info("The following PySide modules were found from the Python files of " + f"the project {self.modules}") + + def _find_and_set_qtquick_modules(self): + """Identify if QtQuick is used in QML files and add them as dependency + """ + extra_modules = [] + if not self.qml_modules: + self.qml_modules = set(run_qmlimportscanner(qml_files=self.qml_files, + dry_run=self.dry_run)) + + if "QtQuick" in self.qml_modules: + extra_modules.append("Quick") + + if "QtQuick.Controls" in self.qml_modules: + extra_modules.append("QuickControls2") + + self.modules += extra_modules + + +class DesktopConfig(Config): + """Wrapper class around pysidedeploy.spec, but specific to Desktop deployment + """ + def __init__(self, config_file: Path, source_file: Path, python_exe: Path, dry_run: bool, + existing_config_file: bool = False, extra_ignore_dirs: List[str] = None): + super().__init__(config_file, source_file, python_exe, dry_run, existing_config_file, + extra_ignore_dirs) + self.dependency_reader = QtDependencyReader(dry_run=self.dry_run) + if self.get_value("qt", "modules"): + self.modules = self.get_value("qt", "modules").split(",") + else: + self._find_and_set_pysidemodules() + self._find_and_set_qtquick_modules() + self._find_dependent_qt_modules() + + self._qt_plugins = [] + if self.get_value("qt", "plugins"): + self._qt_plugins = self.get_value("qt", "plugins").split(",") + else: + self.qt_plugins = self.dependency_reader.find_plugin_dependencies(self.modules, + python_exe) + + self._permissions = [] + if sys.platform == "darwin": + nuitka_macos_permissions = self.get_value("nuitka", "macos.permissions") + if nuitka_macos_permissions: + self._permissions = nuitka_macos_permissions.split(",") + else: + self._find_and_set_permissions() + + @property + def qt_plugins(self): + return self._qt_plugins + + @qt_plugins.setter + def qt_plugins(self, qt_plugins): + self._qt_plugins = qt_plugins + self.set_value("qt", "plugins", ",".join(qt_plugins)) + + @property + def permissions(self): + return self._permissions + + @permissions.setter + def permissions(self, permissions): + self._permissions = permissions + self.set_value("nuitka", "macos.permissions", ",".join(permissions)) + + def _find_dependent_qt_modules(self): + """ + Given pysidedeploy_config.modules, find all the other dependent Qt modules. + """ + all_modules = set(self.modules) + + if not self.dependency_reader.lib_reader: + warnings.warn(f"[DEPLOY] Unable to find {self.dependency_reader.lib_reader_name}. This " + "tool helps to find the Qt module dependencies of the application. " + "Skipping checking for dependencies.", category=RuntimeWarning) + return + + for module_name in self.modules: + self.dependency_reader.find_dependencies(module=module_name, used_modules=all_modules) + + self.modules = list(all_modules) + + def _find_and_set_permissions(self): + """ + Finds and sets the usage description string required for each permission requested by the + macOS application. + """ + permissions = [] + perm_categories = find_permission_categories(project_dir=self.project_dir, + extra_ignore_dirs=self.extra_ignore_dirs, + project_data=self.project_data) + + perm_categories_str = ",".join(perm_categories) + logging.info(f"[DEPLOY] Usage descriptions for the {perm_categories_str} will be added to " + "the Info.plist file of the macOS application bundle") + + # handling permissions + for perm_category in perm_categories: + if perm_category in PERMISSION_MAP: + permissions.append(PERMISSION_MAP[perm_category]) + + self.permissions = permissions diff --git a/sources/pyside-tools/deploy_lib/default.spec b/sources/pyside-tools/deploy_lib/default.spec index 7ca2edfe..57f99eca 100644 --- a/sources/pyside-tools/deploy_lib/default.spec +++ b/sources/pyside-tools/deploy_lib/default.spec @@ -27,7 +27,7 @@ python_path = # python packages to install # ordered-set: increase compile time performance of nuitka packaging # zstandard: provides final executable size optimization -packages = nuitka==1.8.0,ordered_set,zstandard +packages = Nuitka==2.3.2 # buildozer: for deploying Android application android_packages = buildozer==1.5.0,cython==0.29.33 @@ -41,6 +41,12 @@ qml_files = # excluded qml plugin binaries excluded_qml_plugins = +# Qt modules used. Comma separated +modules = + +# Qt plugins used by the application +plugins = + [android] # path to PySide wheel @@ -50,10 +56,15 @@ wheel_pyside = wheel_shiboken = # plugins to be copied to libs folder of the packaged application. Comma separated -plugins = platforms_qtforandroid +plugins = [nuitka] +# usage description for permissions requested by the app as found in the Info.plist file +# of the app bundle +# eg: NSCameraUsageDescription:CameraAccess +macos.permissions = + # (str) specify any extra nuitka arguments # eg: extra_args = --show-modules --follow-stdlib extra_args = --quiet --noinclude-qt-translations @@ -77,12 +88,9 @@ ndk_path = # if empty uses default sdk path downloaded by buildozer sdk_path = -# modules used. Comma separated -modules = - # other libraries to be loaded. Comma separated. # loaded at app startup -local_libs = plugins_platforms_qtforandroid +local_libs = # architecture of deployed platform # possible values: ["aarch64", "armv7a", "i686", "x86_64"] diff --git a/sources/pyside-tools/deploy_lib/dependency_util.py b/sources/pyside-tools/deploy_lib/dependency_util.py new file mode 100644 index 00000000..2d5b188d --- /dev/null +++ b/sources/pyside-tools/deploy_lib/dependency_util.py @@ -0,0 +1,319 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +import ast +import re +import os +import site +import json +import warnings +import logging +import shutil +import sys +from pathlib import Path +from typing import List, Set +from functools import lru_cache + +from . import IMPORT_WARNING_PYSIDE, run_command + + +@lru_cache(maxsize=None) +def get_py_files(project_dir: Path, extra_ignore_dirs: List[Path] = None, project_data=None): + """Finds and returns all the Python files in the project + """ + py_candidates = [] + ignore_dirs = ["__pycache__", "env", "venv", "deployment"] + + if project_data: + py_candidates = project_data.python_files + ui_candidates = project_data.ui_files + qrc_candidates = project_data.qrc_files + + def add_uic_qrc_candidates(candidates, candidate_type): + possible_py_candidates = [(file.parent / f"{candidate_type}_{file.stem}.py") + for file in candidates + if (file.parent / f"{candidate_type}_{file.stem}.py").exists() + ] + + if len(possible_py_candidates) != len(candidates): + warnings.warn(f"[DEPLOY] The number of {candidate_type} files and their " + "corresponding Python files don't match.", + category=RuntimeWarning) + + py_candidates.extend(possible_py_candidates) + + if ui_candidates: + add_uic_qrc_candidates(ui_candidates, "ui") + + if qrc_candidates: + add_uic_qrc_candidates(qrc_candidates, "qrc") + + return py_candidates + + # incase there is not .pyproject file, search all python files in project_dir, except + # ignore_dirs + if extra_ignore_dirs: + ignore_dirs.extend(extra_ignore_dirs) + + # find relevant .py files + _walk = os.walk(project_dir) + for root, dirs, files in _walk: + dirs[:] = [d for d in dirs if d not in ignore_dirs and not d.startswith(".")] + for py_file in files: + if py_file.endswith(".py"): + py_candidates.append(Path(root) / py_file) + + return py_candidates + + +@lru_cache(maxsize=None) +def get_ast(py_file: Path): + """Given a Python file returns the abstract syntax tree + """ + contents = py_file.read_text(encoding="utf-8") + try: + tree = ast.parse(contents) + except SyntaxError: + print(f"[DEPLOY] Unable to parse {py_file}") + return tree + + +def find_permission_categories(project_dir: Path, extra_ignore_dirs: List[Path] = None, + project_data=None): + """Given the project directory, finds all the permission categories required by the + project. eg: Camera, Bluetooth, Contacts etc. + + Note: This function is only relevant for mac0S deployment. + """ + all_perm_categories = set() + mod_pattern = re.compile("Q(?P.*)Permission") + + def pyside_permission_imports(py_file: Path): + perm_categories = [] + try: + tree = get_ast(py_file) + for node in ast.walk(tree): + if isinstance(node, ast.ImportFrom): + main_mod_name = node.module + if main_mod_name == "PySide6.QtCore": + # considers 'from PySide6.QtCore import QtMicrophonePermission' + for imported_module in node.names: + full_mod_name = imported_module.name + match = mod_pattern.search(full_mod_name) + if match: + mod_name = match.group("mod_name") + perm_categories.append(mod_name) + continue + + if isinstance(node, ast.Import): + for imported_module in node.names: + full_mod_name = imported_module.name + if full_mod_name == "PySide6": + logging.warning(IMPORT_WARNING_PYSIDE.format(str(py_file))) + except Exception as e: + raise RuntimeError(f"[DEPLOY] Finding permission categories failed on file " + f"{str(py_file)} with error {e}") + + return set(perm_categories) + + py_candidates = get_py_files(project_dir, extra_ignore_dirs, project_data) + for py_candidate in py_candidates: + all_perm_categories = all_perm_categories.union(pyside_permission_imports(py_candidate)) + + if not all_perm_categories: + ValueError("[DEPLOY] No permission categories were found for macOS app bundle creation.") + + return all_perm_categories + + +def find_pyside_modules(project_dir: Path, extra_ignore_dirs: List[Path] = None, + project_data=None): + """ + Searches all the python files in the project to find all the PySide modules used by + the application. + """ + all_modules = set() + mod_pattern = re.compile("PySide6.Qt(?P.*)") + + def pyside_module_imports(py_file: Path): + modules = [] + try: + tree = get_ast(py_file) + for node in ast.walk(tree): + if isinstance(node, ast.ImportFrom): + main_mod_name = node.module + if main_mod_name.startswith("PySide6"): + if main_mod_name == "PySide6": + # considers 'from PySide6 import QtCore' + for imported_module in node.names: + full_mod_name = imported_module.name + if full_mod_name.startswith("Qt"): + modules.append(full_mod_name[2:]) + continue + + # considers 'from PySide6.QtCore import Qt' + match = mod_pattern.search(main_mod_name) + if match: + mod_name = match.group("mod_name") + modules.append(mod_name) + else: + logging.warning(( + f"[DEPLOY] Unable to find module name from {ast.dump(node)}")) + + if isinstance(node, ast.Import): + for imported_module in node.names: + full_mod_name = imported_module.name + if full_mod_name == "PySide6": + logging.warning(IMPORT_WARNING_PYSIDE.format(str(py_file))) + except Exception as e: + raise RuntimeError(f"[DEPLOY] Finding module import failed on file {str(py_file)} with " + f"error {e}") + + return set(modules) + + py_candidates = get_py_files(project_dir, extra_ignore_dirs, project_data) + for py_candidate in py_candidates: + all_modules = all_modules.union(pyside_module_imports(py_candidate)) + + if not all_modules: + ValueError("[DEPLOY] No PySide6 modules were found") + + return list(all_modules) + + +class QtDependencyReader: + def __init__(self, dry_run: bool = False) -> None: + self.dry_run = dry_run + self.lib_reader_name = None + self.qt_module_path_pattern = None + self.lib_pattern = None + self.command = None + self.qt_libs_dir = None + + if sys.platform == "linux": + self.lib_reader_name = "readelf" + self.qt_module_path_pattern = "libQt6{module}.so.6" + self.lib_pattern = re.compile("libQt6(?P.*).so.6") + self.command_args = "-d" + elif sys.platform == "darwin": + self.lib_reader_name = "dyld_info" + self.qt_module_path_pattern = "Qt{module}.framework/Versions/A/Qt{module}" + self.lib_pattern = re.compile("@rpath/Qt(?P.*).framework/Versions/A/") + self.command_args = "-dependents" + elif sys.platform == "win32": + self.lib_reader_name = "dumpbin" + self.qt_module_path_pattern = "Qt6{module}.dll" + self.lib_pattern = re.compile("Qt6(?P.*).dll") + self.command_args = "/dependents" + else: + print(f"[DEPLOY] Deployment on unsupported platfrom {sys.platform}") + sys.exit(1) + + self.pyside_install_dir = None + self.qt_libs_dir = self.get_qt_libs_dir() + self._lib_reader = shutil.which(self.lib_reader_name) + + def get_qt_libs_dir(self): + """ + Finds the path to the Qt libs directory inside PySide6 package installation + """ + for possible_site_package in site.getsitepackages(): + if possible_site_package.endswith("site-packages"): + self.pyside_install_dir = Path(possible_site_package) / "PySide6" + + if not self.pyside_install_dir: + print("Unable to find site-packages. Exiting ...") + sys.exit(-1) + + if sys.platform == "win32": + return self.pyside_install_dir + + return self.pyside_install_dir / "Qt" / "lib" # for linux and macOS + + @property + def lib_reader(self): + return self._lib_reader + + def find_dependencies(self, module: str, used_modules: Set[str] = None): + """ + Given a Qt module, find all the other Qt modules it is dependent on and add it to the + 'used_modules' set + """ + qt_module_path = self.qt_libs_dir / self.qt_module_path_pattern.format(module=module) + if not qt_module_path.exists(): + warnings.warn(f"[DEPLOY] {qt_module_path.name} not found in {str(qt_module_path)}." + "Skipping finding its dependencies.", category=RuntimeWarning) + return + + lib_pattern = re.compile(self.lib_pattern) + command = [self.lib_reader, self.command_args, str(qt_module_path)] + # print the command if dry_run is True. + # Normally run_command is going to print the command in dry_run mode. But, this is a + # special case where we need to print the command as well as to run it. + if self.dry_run: + command_str = " ".join(command) + print(command_str + "\n") + + # We need to run this even for dry run, to see the full Nuitka command being executed + _, output = run_command(command=command, dry_run=False, fetch_output=True) + + dependent_modules = set() + for line in output.splitlines(): + line = line.decode("utf-8").lstrip() + if sys.platform == "darwin": + if line.endswith(f"Qt{module} [arm64]:"): + # macOS Qt frameworks bundles have both x86_64 and arm64 architectures + # We only need to consider one as the dependencies are redundant + break + elif line.endswith(f"Qt{module} [X86_64]:"): + # this line needs to be skipped because it matches with the pattern + # and is related to the module itself, not the dependencies of the module + continue + elif sys.platform == "win32" and line.startswith("Summary"): + # the dependencies would be found before the `Summary` line + break + match = lib_pattern.search(line) + if match: + dep_module = match.group("mod_name") + dependent_modules.add(dep_module) + if dep_module not in used_modules: + used_modules.add(dep_module) + self.find_dependencies(module=dep_module, used_modules=used_modules) + + if dependent_modules: + logging.info(f"[DEPLOY] Following dependencies found for {module}: {dependent_modules}") + else: + logging.info(f"[DEPLOY] No Qt dependencies found for {module}") + + def find_plugin_dependencies(self, used_modules: List[str], python_exe: Path) -> List[str]: + """ + Given the modules used by the application, returns all the required plugins + """ + plugins = set() + pyside_wheels = ["PySide6_Essentials", "PySide6_Addons"] + # TODO from 3.12 use list(dist.name for dist in importlib.metadata.distributions()) + _, installed_packages = run_command(command=[str(python_exe), "-m", "pip", "freeze"], + dry_run=False, fetch_output=True) + installed_packages = [p.decode().split('==')[0] for p in installed_packages.split()] + for pyside_wheel in pyside_wheels: + if pyside_wheel not in installed_packages: + # the wheel is not installed and hence no plugins are checked for its modules + logging.warning((f"[DEPLOY] The package {pyside_wheel} is not installed. ")) + continue + pyside_mod_plugin_json_name = f"{pyside_wheel}.json" + pyside_mod_plugin_json_file = self.pyside_install_dir / pyside_mod_plugin_json_name + if not pyside_mod_plugin_json_file.exists(): + warnings.warn(f"[DEPLOY] Unable to find {pyside_mod_plugin_json_file}.", + category=RuntimeWarning) + continue + + # convert the json to dict + pyside_mod_dict = {} + with open(pyside_mod_plugin_json_file) as pyside_json: + pyside_mod_dict = json.load(pyside_json) + + # find all the plugins in the modules + for module in used_modules: + plugins.update(pyside_mod_dict.get(module, [])) + + return list(plugins) diff --git a/sources/pyside-tools/deploy_lib/deploy_util.py b/sources/pyside-tools/deploy_lib/deploy_util.py index a8ca5861..e8b05e99 100644 --- a/sources/pyside-tools/deploy_lib/deploy_util.py +++ b/sources/pyside-tools/deploy_lib/deploy_util.py @@ -8,7 +8,6 @@ from pathlib import Path from . import EXE_FORMAT from .config import Config -from .python_helper import PythonExecutable def config_option_exists(): @@ -62,49 +61,6 @@ def create_config_file(dry_run: bool = False, config_file: Path = None, main_fil return config_file -def setup_python(dry_run: bool, force: bool, init: bool): - """ - Sets up Python venv for deployment, and return a wrapper around the venv environment - """ - python = None - response = "yes" - # checking if inside virtual environment - if not PythonExecutable.is_venv() and not force and not dry_run and not init: - response = input(("You are not using a virtual environment. pyside6-deploy needs to install" - " a few Python packages for deployment to work seamlessly. \n" - "Proceed? [Y/n]")) - - if response.lower() in ["no", "n"]: - print("[DEPLOY] Exiting ...") - sys.exit(0) - - python = PythonExecutable(dry_run=dry_run) - logging.info(f"[DEPLOY] Using python at {sys.executable}") - - return python - - -def install_python_dependencies(config: Config, python: PythonExecutable, init: bool, - packages: str, is_android: bool = False): - """ - Installs the python package dependencies for the target deployment platform - """ - packages = config.get_value("python", packages).split(",") - if not init: - # install packages needed for deployment - logging.info("[DEPLOY] Installing dependencies") - python.install(packages=packages) - # nuitka requires patchelf to make patchelf rpath changes for some Qt files - if sys.platform.startswith("linux") and not is_android: - python.install(packages=["patchelf"]) - elif is_android: - # install only buildozer - logging.info("[DEPLOY] Installing buildozer") - buildozer_package_with_version = ([package for package in packages - if package.startswith("buildozer")]) - python.install(packages=list(buildozer_package_with_version)) - - def finalize(config: Config): """ Copy the executable into the final location @@ -112,6 +68,10 @@ def finalize(config: Config): """ generated_exec_path = config.generated_files_path / (config.source_file.stem + EXE_FORMAT) if generated_exec_path.exists() and config.exe_dir: - shutil.copy(generated_exec_path, config.exe_dir) + if sys.platform == "darwin": + shutil.copytree(generated_exec_path, config.exe_dir / (config.title + EXE_FORMAT), + dirs_exist_ok=True) + else: + shutil.copy(generated_exec_path, config.exe_dir) print("[DEPLOY] Executed file created in " f"{str(config.exe_dir / (config.source_file.stem + EXE_FORMAT))}") diff --git a/sources/pyside-tools/deploy_lib/nuitka_helper.py b/sources/pyside-tools/deploy_lib/nuitka_helper.py index ae5834b6..ac9a83f3 100644 --- a/sources/pyside-tools/deploy_lib/nuitka_helper.py +++ b/sources/pyside-tools/deploy_lib/nuitka_helper.py @@ -17,6 +17,29 @@ class Nuitka: def __init__(self, nuitka): self.nuitka = nuitka + # plugins to ignore. The sensible plugins are include by default by Nuitka for PySide6 + # application deployment + self.qt_plugins_to_ignore = ["imageformats", # being Nuitka `sensible`` plugins + "iconengines", + "mediaservice", + "printsupport", + "platforms", + "platformthemes", + "styles", + "wayland-shell-integration", + "wayland-decoration-client", + "wayland-graphics-integration-client", + "egldeviceintegrations", + "xcbglintegrations", + "tls", # end Nuitka `sensible` plugins + "generic" # plugins that error with Nuitka + ] + + # .webp are considered to be dlls by Nuitka instead of data files causing + # the packaging to fail + # https://github.com/Nuitka/Nuitka/issues/2854 + # TODO: Remove .webp when the issue is fixed + self.files_to_ignore = [".cpp.o", ".qsb", ".webp"] @staticmethod def icon_option(): @@ -28,11 +51,22 @@ class Nuitka: return "--macos-app-icon" def create_executable(self, source_file: Path, extra_args: str, qml_files: List[Path], - excluded_qml_plugins: List[str], icon: str, dry_run: bool): + qt_plugins: List[str], excluded_qml_plugins: List[str], icon: str, + dry_run: bool, permissions: List[str]): + qt_plugins = [plugin for plugin in qt_plugins if plugin not in self.qt_plugins_to_ignore] extra_args = extra_args.split() + + if sys.platform == "darwin": + # create an app bundle + extra_args.extend(["--standalone", "--macos-create-app-bundle"]) + permission_pattern = "--macos-app-protected-resource={permission}" + for permission in permissions: + extra_args.append(permission_pattern.format(permission=permission)) + else: + extra_args.append("--onefile") + qml_args = [] if qml_files: - qml_args.append("--include-qt-plugins=all") # This will generate options for each file using: # --include-data-files=ABSOLUTE_PATH_TO_FILE=RELATIVE_PATH_TO ROOT # for each file. This will preserve the directory structure of QML resources. @@ -41,6 +75,11 @@ class Nuitka: f"./{qml_file.resolve().relative_to(source_file.parent)}" for qml_file in qml_files] ) + # add qml plugin. The `qml`` plugin name is not present in the module json files shipped + # with Qt and hence not in `qt_plugins``. However, Nuitka uses the 'qml' plugin name to + # include the necessary qml plugins. There we have to add it explicitly for a qml + # application + qt_plugins.append("qml") if excluded_qml_plugins: prefix = "lib" if sys.platform != "win32" else "" @@ -48,6 +87,14 @@ class Nuitka: dll_name = plugin.replace("Qt", f"Qt{MAJOR_VERSION}") qml_args.append(f"--noinclude-dlls={prefix}{dll_name}*") + # Exclude .qen json files from QtQuickEffectMaker + # These files are not relevant for PySide6 applications + qml_args.append("--noinclude-dlls=*/qml/QtQuickEffectMaker/*") + + # Exclude files that cannot be processed by Nuitka + for file in self.files_to_ignore: + extra_args.append(f"--noinclude-dlls=*{file}") + output_dir = source_file.parent / "deployment" if not dry_run: output_dir.mkdir(parents=True, exist_ok=True) @@ -55,12 +102,17 @@ class Nuitka: command = self.nuitka + [ os.fspath(source_file), "--follow-imports", - "--onefile", "--enable-plugin=pyside6", f"--output-dir={output_dir}", ] + command.extend(extra_args + qml_args) command.append(f"{self.__class__.icon_option()}={icon}") + if qt_plugins: + # sort qt_plugins so that the result is definitive when testing + qt_plugins.sort() + qt_plugins_str = ",".join(qt_plugins) + command.append(f"--include-qt-plugins={qt_plugins_str}") command_str, _ = run_command(command=command, dry_run=dry_run) return command_str diff --git a/sources/pyside-tools/deploy_lib/python_helper.py b/sources/pyside-tools/deploy_lib/python_helper.py index 6ec3b64f..7cbf323e 100644 --- a/sources/pyside-tools/deploy_lib/python_helper.py +++ b/sources/pyside-tools/deploy_lib/python_helper.py @@ -1,124 +1,15 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -import ast import logging import os -import re import sys -import warnings -from typing import List + from importlib import util from importlib.metadata import version from pathlib import Path -from . import Nuitka, run_command - -IMPORT_WARNING_PYSIDE = (f"[DEPLOY] Found 'import PySide6' in file {0}" - ". Use 'from PySide6 import ' or pass the module" - " needed using --extra-modules command line argument") - - -def find_pyside_modules(project_dir: Path, extra_ignore_dirs: List[Path] = None, - project_data=None): - """ - Searches all the python files in the project to find all the PySide modules used by - the application. - """ - all_modules = set() - mod_pattern = re.compile("PySide6.Qt(?P.*)") - - def pyside_imports(py_file: Path): - modules = [] - contents = py_file.read_text(encoding="utf-8") - try: - tree = ast.parse(contents) - for node in ast.walk(tree): - if isinstance(node, ast.ImportFrom): - main_mod_name = node.module - if main_mod_name.startswith("PySide6"): - if main_mod_name == "PySide6": - # considers 'from PySide6 import QtCore' - for imported_module in node.names: - full_mod_name = imported_module.name - if full_mod_name.startswith("Qt"): - modules.append(full_mod_name[2:]) - continue - - # considers 'from PySide6.QtCore import Qt' - match = mod_pattern.search(main_mod_name) - if match: - mod_name = match.group("mod_name") - modules.append(mod_name) - else: - logging.warning(( - f"[DEPLOY] Unable to find module name from{ast.dump(node)}")) - - if isinstance(node, ast.Import): - for imported_module in node.names: - full_mod_name = imported_module.name - if full_mod_name == "PySide6": - logging.warning(IMPORT_WARNING_PYSIDE.format(str(py_file))) - except Exception as e: - raise RuntimeError(f"[DEPLOY] Finding module import failed on file {str(py_file)} with " - f"error {e}") - - return set(modules) - - py_candidates = [] - ignore_dirs = ["__pycache__", "env", "venv", "deployment"] - - if project_data: - py_candidates = project_data.python_files - ui_candidates = project_data.ui_files - qrc_candidates = project_data.qrc_files - ui_py_candidates = None - qrc_ui_candidates = None - - if ui_candidates: - ui_py_candidates = [(file.parent / f"ui_{file.stem}.py") for file in ui_candidates - if (file.parent / f"ui_{file.stem}.py").exists()] - - if len(ui_py_candidates) != len(ui_candidates): - warnings.warn("[DEPLOY] The number of uic files and their corresponding Python" - " files don't match.", category=RuntimeWarning) - - py_candidates.extend(ui_py_candidates) - - if qrc_candidates: - qrc_ui_candidates = [(file.parent / f"rc_{file.stem}.py") for file in qrc_candidates - if (file.parent / f"rc_{file.stem}.py").exists()] - - if len(qrc_ui_candidates) != len(qrc_candidates): - warnings.warn("[DEPLOY] The number of qrc files and their corresponding Python" - " files don't match.", category=RuntimeWarning) - - py_candidates.extend(qrc_ui_candidates) - - for py_candidate in py_candidates: - all_modules = all_modules.union(pyside_imports(py_candidate)) - return list(all_modules) - - # incase there is not .pyproject file, search all python files in project_dir, except - # ignore_dirs - if extra_ignore_dirs: - ignore_dirs.extend(extra_ignore_dirs) - - # find relevant .py files - _walk = os.walk(project_dir) - for root, dirs, files in _walk: - dirs[:] = [d for d in dirs if d not in ignore_dirs and not d.startswith(".")] - for py_file in files: - if py_file.endswith(".py"): - py_candidates.append(Path(root) / py_file) - - for py_candidate in py_candidates: - all_modules = all_modules.union(pyside_imports(py_candidate)) - - if not all_modules: - ValueError("[DEPLOY] No PySide6 modules were found") - - return list(all_modules) +from . import Config, run_command class PythonExecutable: @@ -126,10 +17,28 @@ class PythonExecutable: Wrapper class around Python executable """ - def __init__(self, python_path=None, dry_run=False): - self.exe = python_path if python_path else Path(sys.executable) + def __init__(self, python_path: Path = None, dry_run: bool = False, init: bool = False, + force: bool = False): + self.dry_run = dry_run - self.nuitka = Nuitka(nuitka=[os.fspath(self.exe), "-m", "nuitka"]) + self.init = init + if not python_path: + response = "yes" + # checking if inside virtual environment + if not self.is_venv() and not force and not self.dry_run and not self.init: + response = input(("You are not using a virtual environment. pyside6-deploy needs " + "to install a few Python packages for deployment to work " + "seamlessly. \n Proceed? [Y/n]")) + + if response.lower() in ["no", "n"]: + print("[DEPLOY] Exiting ...") + sys.exit(0) + + self.exe = Path(sys.executable) + else: + self.exe = python_path + + logging.info(f"[DEPLOY] Using Python at {str(self.exe)}") @property def exe(self): @@ -193,16 +102,21 @@ class PythonExecutable: def is_installed(self, package): return bool(util.find_spec(package)) - def create_executable(self, source_file: Path, extra_args: str, config): - if config.qml_files: - logging.info(f"[DEPLOY] Included QML files: {config.qml_files}") - - command_str = self.nuitka.create_executable(source_file=source_file, - extra_args=extra_args, - qml_files=config.qml_files, - excluded_qml_plugins=(config. - excluded_qml_plugins), - icon=config.icon, - dry_run=self.dry_run) - - return command_str + def install_dependencies(self, config: Config, packages: str, is_android: bool = False): + """ + Installs the python package dependencies for the target deployment platform + """ + packages = config.get_value("python", packages).split(",") + if not self.init: + # install packages needed for deployment + logging.info("[DEPLOY] Installing dependencies") + self.install(packages=packages) + # nuitka requires patchelf to make patchelf rpath changes for some Qt files + if sys.platform.startswith("linux") and not is_android: + self.install(packages=["patchelf"]) + elif is_android: + # install only buildozer + logging.info("[DEPLOY] Installing buildozer") + buildozer_package_with_version = ([package for package in packages + if package.startswith("buildozer")]) + self.install(packages=list(buildozer_package_with_version)) diff --git a/sources/pyside-tools/metaobjectdump.py b/sources/pyside-tools/metaobjectdump.py index bc84708c..0970f997 100644 --- a/sources/pyside-tools/metaobjectdump.py +++ b/sources/pyside-tools/metaobjectdump.py @@ -39,6 +39,7 @@ QOBJECT_DERIVED = ["QObject", "QQuickItem", "QQuickPaintedItem"] + ITEM_MODELS AstDecorator = Union[ast.Name, ast.Call] +AstPySideTypeSpec = Union[ast.Name, ast.Constant] ClassList = List[dict] @@ -96,12 +97,20 @@ def _parse_assignment(node: ast.Assign) -> Tuple[Optional[str], Optional[ast.AST return (None, None) +def _parse_pyside_type(type_spec: AstPySideTypeSpec) -> str: + """Parse type specification of a Slot/Property decorator. Usually a type, + but can also be a string constant with a C++ type name.""" + if isinstance(type_spec, ast.Constant): + return type_spec.value + return _python_to_cpp_type(_name(type_spec)) + + def _parse_call_args(call: ast.Call): """Parse arguments of a Signal call/Slot decorator (type list).""" result: Arguments = [] for n, arg in enumerate(call.args): par_name = f"a{n+1}" - par_type = _python_to_cpp_type(_name(arg)) + par_type = _parse_pyside_type(arg) result.append({"name": par_name, "type": par_type}) return result @@ -264,7 +273,7 @@ class MetaObjectDumpVisitor(ast.NodeVisitor): elif name == "QmlNamedElement" and node.args: name = node.args[0].value class_decorators.append(_decorator("QML.Element", name)) - else: + elif name.startswith('Q'): print('Unknown decorator with parameters:', name, file=sys.stderr) return @@ -277,7 +286,7 @@ class MetaObjectDumpVisitor(ast.NodeVisitor): class_decorators.append(_decorator("QML.Singleton", "true")) elif name == "QmlAnonymous": class_decorators.append(_decorator("QML.Element", "anonymous")) - else: + elif name.startswith('Q'): print('Unknown decorator:', name, file=sys.stderr) return @@ -311,8 +320,8 @@ class MetaObjectDumpVisitor(ast.NodeVisitor): if isinstance(node, ast.Call): name = _name(node.func) if name == "Property": # Property getter - if node.args: # 1st is type - type = _python_to_cpp_type(_name(node.args[0])) + if node.args: # 1st is type/type string + type = _parse_pyside_type(node.args[0]) prop = self._create_property_entry(func_name, type, func_name) _parse_property_kwargs(node.keywords, prop) diff --git a/sources/pyside-tools/project.py b/sources/pyside-tools/project.py index 621bf1ac..3706a298 100644 --- a/sources/pyside-tools/project.py +++ b/sources/pyside-tools/project.py @@ -27,15 +27,17 @@ from argparse import ArgumentParser, RawTextHelpFormatter from project import (QmlProjectData, check_qml_decorators, is_python_file, QMLDIR_FILE, MOD_CMD, METATYPES_JSON_SUFFIX, + SHADER_SUFFIXES, TRANSLATION_SUFFIX, requires_rebuild, run_command, remove_path, ProjectData, resolve_project_file, new_project, - ProjectType) + ProjectType, ClOptions) MODE_HELP = """build Builds the project run Builds the project and runs the first file") clean Cleans the build artifacts") qmllint Runs the qmllint tool deploy Deploys the application +lupdate Updates translation (.ts) files new-ui Creates a new QtWidgets project with a Qt Designer-based main window new-widget Creates a new QtWidgets project with a main window new-quick Creates a new QtQuick project @@ -43,8 +45,11 @@ new-quick Creates a new QtQuick project UIC_CMD = "pyside6-uic" RCC_CMD = "pyside6-rcc" +LRELEASE_CMD = "pyside6-lrelease" +LUPDATE_CMD = "pyside6-lupdate" QMLTYPEREGISTRAR_CMD = "pyside6-qmltyperegistrar" QMLLINT_CMD = "pyside6-qmllint" +QSB_CMD = "pyside6-qsb" DEPLOY_CMD = "pyside6-deploy" NEW_PROJECT_TYPES = {"new-quick": ProjectType.QUICK, @@ -52,12 +57,23 @@ NEW_PROJECT_TYPES = {"new-quick": ProjectType.QUICK, "new-widget": ProjectType.WIDGET} +def _sort_sources(files: List[Path]) -> List[Path]: + """Sort the sources for building, ensure .qrc is last since it might depend + on generated files.""" + + def key_func(p: Path): + return p.suffix if p.suffix != ".qrc" else ".zzzz" + + return sorted(files, key=key_func) + + class Project: """ Class to wrap the various operations on Project """ def __init__(self, project_file: Path): self.project = ProjectData(project_file=project_file) + self.cl_options = ClOptions() # Files for QML modules using the QmlElement decorators self._qml_module_sources: List[Path] = [] @@ -70,7 +86,7 @@ class Project: """Run a pre-check on Python source files and find the ones with QML decorators (representing a QML module).""" # Quick check for any QML files (to avoid running moc for no reason). - if not opt_qml_module and not self.project.qml_files: + if not self.cl_options.qml_module and not self.project.qml_files: return for file in self.project.files: if is_python_file(file): @@ -92,7 +108,7 @@ class Project: print(self._qml_module_dir) self._qml_dir_file = self._qml_module_dir / QMLDIR_FILE - if not opt_quiet: + if not self.cl_options.quiet: count = len(self._qml_module_sources) print(f"{self.project.project_file.name}, {count} QML file(s)," f" {self._qml_project_data}") @@ -123,13 +139,23 @@ class Project: cmd.extend(self._qml_project_data.registrar_options()) return ([qmltypes_file, cpp_file], cmd) + if file.name.endswith(TRANSLATION_SUFFIX): + qm_file = f"{file.parent}/{file.stem}.qm" + cmd = [LRELEASE_CMD, os.fspath(file), "-qm", qm_file] + return ([Path(qm_file)], cmd) + + if file.suffix in SHADER_SUFFIXES: + qsb_file = f"{file.parent}/{file.stem}.qsb" + cmd = [QSB_CMD, "-o", qsb_file, os.fspath(file)] + return ([Path(qsb_file)], cmd) + return ([], None) def _regenerate_qmldir(self): """Regenerate the 'qmldir' file.""" - if opt_dry_run or not self._qml_dir_file: + if self.cl_options.dry_run or not self._qml_dir_file: return - if opt_force or requires_rebuild(self._qml_module_sources, self._qml_dir_file): + if self.cl_options.force or requires_rebuild(self._qml_module_sources, self._qml_dir_file): with self._qml_dir_file.open("w") as qf: qf.write(f"module {self._qml_project_data.import_name}\n") for f in self._qml_module_dir.glob("*.qmltypes"): @@ -139,7 +165,7 @@ class Project: """Build an artifact.""" artifacts, command = self._get_artifacts(source) for artifact in artifacts: - if opt_force or requires_rebuild([source], artifact): + if self.cl_options.force or requires_rebuild([source], artifact): run_command(command, cwd=self.project.project_file.parent) self._build_file(artifact) # Recurse for QML (json->qmltypes) @@ -149,7 +175,7 @@ class Project: Project(project_file=sub_project_file).build() if self._qml_module_dir: self._qml_module_dir.mkdir(exist_ok=True, parents=True) - for file in self.project.files: + for file in _sort_sources(self.project.files): self._build_file(file) self._regenerate_qmldir() @@ -207,6 +233,24 @@ class Project: cmd.extend([str(self.project.main_file), "-f"]) run_command(cmd, cwd=self.project.project_file.parent) + def lupdate(self): + for sub_project_file in self.project.sub_projects_files: + Project(project_file=sub_project_file).lupdate() + + if not self.project.ts_files: + print(f"{self.project.project_file.name}: No .ts file found.", + file=sys.stderr) + return + + source_files = self.project.python_files + self.project.ui_files + cmd_prefix = [LUPDATE_CMD] + [p.name for p in source_files] + cmd_prefix.append("-ts") + for ts_file in self.project.ts_files: + if requires_rebuild(source_files, ts_file): + cmd = cmd_prefix + cmd.append(ts_file.name) + run_command(cmd, cwd=self.project.project_file.parent) + if __name__ == "__main__": parser = ArgumentParser(description=__doc__, formatter_class=RawTextHelpFormatter) @@ -215,17 +259,16 @@ if __name__ == "__main__": parser.add_argument("--force", "-f", action="store_true", help="Force rebuild") parser.add_argument("--qml-module", "-Q", action="store_true", help="Perform check for QML module") - mode_choices = ["build", "run", "clean", "qmllint", "deploy"] + mode_choices = ["build", "run", "clean", "qmllint", "deploy", "lupdate"] mode_choices.extend(NEW_PROJECT_TYPES.keys()) parser.add_argument("mode", choices=mode_choices, default="build", type=str, help=MODE_HELP) parser.add_argument("file", help="Project file", nargs="?", type=str) options = parser.parse_args() - opt_quiet = options.quiet - opt_dry_run = options.dry_run - opt_force = options.force - opt_qml_module = options.qml_module + cl_options = ClOptions(dry_run=options.dry_run, quiet=options.quiet, force=options.force, + qml_module=options.qml_module) + mode = options.mode new_project_type = NEW_PROJECT_TYPES.get(mode) @@ -250,6 +293,8 @@ if __name__ == "__main__": project.qmllint() elif mode == "deploy": project.deploy() + elif mode == "lupdate": + project.lupdate() else: print(f"Invalid mode {mode}", file=sys.stderr) sys.exit(1) diff --git a/sources/pyside-tools/project/__init__.py b/sources/pyside-tools/project/__init__.py index be99555b..e57a9ff8 100644 --- a/sources/pyside-tools/project/__init__.py +++ b/sources/pyside-tools/project/__init__.py @@ -1,10 +1,7 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -opt_quiet = False -opt_dry_run = False -opt_force = False -opt_qml_module = False +from dataclasses import dataclass QTPATHS_CMD = "qtpaths6" MOD_CMD = "pyside6-metaobjectdump" @@ -18,6 +15,29 @@ QML_IMPORT_MINOR_VERSION = "QML_IMPORT_MINOR_VERSION" QT_MODULES = "QT_MODULES" METATYPES_JSON_SUFFIX = "metatypes.json" +TRANSLATION_SUFFIX = ".ts" +SHADER_SUFFIXES = ".vert", ".frag" + + +class Singleton(type): + _instances = {} + + def __call__(cls, *args, **kwargs): + if cls not in cls._instances: + cls._instances[cls] = super().__call__(*args, **kwargs) + return cls._instances[cls] + + +@dataclass(frozen=True) +class ClOptions(metaclass=Singleton): + """ + Dataclass to store the cl options that needs to be passed as arguments. + """ + dry_run: bool + quiet: bool + force: bool + qml_module: bool + from .utils import (run_command, requires_rebuild, remove_path, package_dir, qtpaths, qt_metatype_json_dir, resolve_project_file) diff --git a/sources/pyside-tools/project/project_data.py b/sources/pyside-tools/project/project_data.py index 416089dc..52e20be3 100644 --- a/sources/pyside-tools/project/project_data.py +++ b/sources/pyside-tools/project/project_data.py @@ -7,9 +7,9 @@ import subprocess import sys from typing import List, Tuple from pathlib import Path -from . import (METATYPES_JSON_SUFFIX, PROJECT_FILE_SUFFIX, qt_metatype_json_dir, - MOD_CMD, QML_IMPORT_MAJOR_VERSION, QML_IMPORT_MINOR_VERSION, QML_IMPORT_NAME, - QT_MODULES) +from . import (METATYPES_JSON_SUFFIX, PROJECT_FILE_SUFFIX, TRANSLATION_SUFFIX, + qt_metatype_json_dir, MOD_CMD, QML_IMPORT_MAJOR_VERSION, + QML_IMPORT_MINOR_VERSION, QML_IMPORT_NAME, QT_MODULES) def is_python_file(file: Path) -> bool: @@ -34,6 +34,8 @@ class ProjectData: self._ui_files: List[Path] = [] # qrc files self._qrc_files: List[Path] = [] + # ts files + self._ts_files: List[Path] = [] with project_file.open("r") as pyf: pyproject = json.load(pyf) @@ -53,6 +55,8 @@ class ProjectData: self._ui_files.append(file) elif file.suffix == ".qrc": self._qrc_files.append(file) + elif file.suffix == TRANSLATION_SUFFIX: + self._ts_files.append(file) if not self.main_file: self._find_main_file() @@ -89,6 +93,10 @@ class ProjectData: def qml_files(self): return self._qml_files + @property + def ts_files(self): + return self._ts_files + @property def sub_projects_files(self): return self._sub_projects_files diff --git a/sources/pyside-tools/project/utils.py b/sources/pyside-tools/project/utils.py index b3fd0358..d2bff65a 100644 --- a/sources/pyside-tools/project/utils.py +++ b/sources/pyside-tools/project/utils.py @@ -6,14 +6,15 @@ import subprocess from pathlib import Path from typing import List, Dict, Optional -from . import opt_dry_run, opt_quiet, QTPATHS_CMD, PROJECT_FILE_SUFFIX +from . import QTPATHS_CMD, PROJECT_FILE_SUFFIX, ClOptions def run_command(command: List[str], cwd: str = None, ignore_fail: bool = False): """Run a command observing quiet/dry run""" - if not opt_quiet or opt_dry_run: + cloptions = ClOptions() + if not cloptions.quiet or cloptions.dry_run: print(" ".join(command)) - if not opt_dry_run: + if not cloptions.dry_run: ex = subprocess.call(command, cwd=cwd) if ex != 0 and not ignore_fail: sys.exit(ex) @@ -42,11 +43,12 @@ def _remove_path_recursion(path: Path): def remove_path(path: Path): """Remove path (file or directory) observing opt_dry_run.""" + cloptions = ClOptions() if not path.exists(): return - if not opt_quiet: + if not cloptions.quiet: print(f"Removing {path.name}...") - if opt_dry_run: + if cloptions.dry_run: return _remove_path_recursion(path) diff --git a/sources/pyside-tools/pyside_tool.py b/sources/pyside-tools/pyside_tool.py index 7daacc22..b369be8a 100644 --- a/sources/pyside-tools/pyside_tool.py +++ b/sources/pyside-tools/pyside_tool.py @@ -227,5 +227,17 @@ def android_deploy(): pyside_script_wrapper("android_deploy.py") +def qsb(): + qt_tool_wrapper("qsb", sys.argv[1:]) + + +def balsam(): + qt_tool_wrapper("balsam", sys.argv[1:]) + + +def balsamui(): + qt_tool_wrapper("balsamui", sys.argv[1:]) + + if __name__ == "__main__": main() diff --git a/sources/pyside-tools/qml.py b/sources/pyside-tools/qml.py index d76e5c5c..5d029f93 100644 --- a/sources/pyside-tools/qml.py +++ b/sources/pyside-tools/qml.py @@ -16,7 +16,7 @@ from typing import List, Set from PySide6.QtCore import QCoreApplication, Qt, QLibraryInfo, QUrl, SignalInstance from PySide6.QtGui import QGuiApplication, QSurfaceFormat from PySide6.QtQml import QQmlApplicationEngine, QQmlComponent -from PySide6.QtQuick import QQuickView, QQuickWindow +from PySide6.QtQuick import QQuickView, QQuickItem from PySide6.QtWidgets import QApplication @@ -227,7 +227,7 @@ if __name__ == "__main__": sys.exit(-1) qquick_view = False - if isinstance(rootObjects[0], QQuickWindow) and qquick_present: + if isinstance(rootObjects[0], QQuickItem) and qquick_present: logging.info("qml: loading with QQuickView") viewer = QQuickView() viewer.setSource(qml_file) diff --git a/sources/pyside6/.cmake.conf b/sources/pyside6/.cmake.conf index 66542fa5..8f6fb6ec 100644 --- a/sources/pyside6/.cmake.conf +++ b/sources/pyside6/.cmake.conf @@ -1,5 +1,5 @@ set(pyside_MAJOR_VERSION "6") -set(pyside_MINOR_VERSION "6") +set(pyside_MINOR_VERSION "7") set(pyside_MICRO_VERSION "2") set(pyside_PRE_RELEASE_VERSION_TYPE "") set(pyside_PRE_RELEASE_VERSION "") diff --git a/sources/pyside6/PySide6/Qt3DAnimation/CMakeLists.txt b/sources/pyside6/PySide6/Qt3DAnimation/CMakeLists.txt index 5b6256fc..d9169924 100644 --- a/sources/pyside6/PySide6/Qt3DAnimation/CMakeLists.txt +++ b/sources/pyside6/PySide6/Qt3DAnimation/CMakeLists.txt @@ -19,6 +19,7 @@ ${Qt3DAnimation_GEN_DIR}/qt3danimation_qanimationcliploader_wrapper.cpp ${Qt3DAnimation_GEN_DIR}/qt3danimation_qanimationcontroller_wrapper.cpp ${Qt3DAnimation_GEN_DIR}/qt3danimation_qanimationgroup_wrapper.cpp ${Qt3DAnimation_GEN_DIR}/qt3danimation_qblendedclipanimator_wrapper.cpp +${Qt3DAnimation_GEN_DIR}/qt3danimation_qcallbackmapping_wrapper.cpp ${Qt3DAnimation_GEN_DIR}/qt3danimation_qchannel_wrapper.cpp ${Qt3DAnimation_GEN_DIR}/qt3danimation_qchannelcomponent_wrapper.cpp ${Qt3DAnimation_GEN_DIR}/qt3danimation_qchannelmapper_wrapper.cpp @@ -42,12 +43,14 @@ set(Qt3DAnimation_include_dirs ${pyside6_SOURCE_DIR} ${Qt${QT_MAJOR_VERSION}Core_INCLUDE_DIRS} ${Qt${QT_MAJOR_VERSION}Gui_INCLUDE_DIR} + ${Qt${QT_MAJOR_VERSION}OpenGL_INCLUDE_DIRS} ${Qt${QT_MAJOR_VERSION}3DCore_INCLUDE_DIRS} ${Qt${QT_MAJOR_VERSION}3DRender_INCLUDE_DIRS} ${Qt${QT_MAJOR_VERSION}3DAnimation_INCLUDE_DIRS} ${libpyside_SOURCE_DIR} ${QtCore_GEN_DIR} ${QtGui_GEN_DIR} + ${QtOpenGL_GEN_DIR} ${Qt3DCore_GEN_DIR} ${Qt3DRender_GEN_DIR} ${Qt3DAnimation_GEN_DIR}) diff --git a/sources/pyside6/PySide6/Qt3DAnimation/typesystem_3danimation.xml b/sources/pyside6/PySide6/Qt3DAnimation/typesystem_3danimation.xml index f93a51b5..61af82d6 100644 --- a/sources/pyside6/PySide6/Qt3DAnimation/typesystem_3danimation.xml +++ b/sources/pyside6/PySide6/Qt3DAnimation/typesystem_3danimation.xml @@ -4,7 +4,8 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + @@ -36,6 +37,7 @@ + diff --git a/sources/pyside6/PySide6/Qt3DCore/CMakeLists.txt b/sources/pyside6/PySide6/Qt3DCore/CMakeLists.txt index 5493c687..cd2699f1 100644 --- a/sources/pyside6/PySide6/Qt3DCore/CMakeLists.txt +++ b/sources/pyside6/PySide6/Qt3DCore/CMakeLists.txt @@ -19,6 +19,7 @@ ${Qt3DCore_GEN_DIR}/qt3dcore_qbackendnodemapper_wrapper.cpp ${Qt3DCore_GEN_DIR}/qt3dcore_qboundingvolume_wrapper.cpp ${Qt3DCore_GEN_DIR}/qt3dcore_qbuffer_wrapper.cpp ${Qt3DCore_GEN_DIR}/qt3dcore_qcomponent_wrapper.cpp +${Qt3DCore_GEN_DIR}/qt3dcore_qcoreaspect_wrapper.cpp ${Qt3DCore_GEN_DIR}/qt3dcore_qcoresettings_wrapper.cpp ${Qt3DCore_GEN_DIR}/qt3dcore_qentity_wrapper.cpp ${Qt3DCore_GEN_DIR}/qt3dcore_qgeometry_wrapper.cpp diff --git a/sources/pyside6/PySide6/Qt3DCore/typesystem_3dcore.xml b/sources/pyside6/PySide6/Qt3DCore/typesystem_3dcore.xml index cc17e246..985c459b 100644 --- a/sources/pyside6/PySide6/Qt3DCore/typesystem_3dcore.xml +++ b/sources/pyside6/PySide6/Qt3DCore/typesystem_3dcore.xml @@ -4,7 +4,8 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + + diff --git a/sources/pyside6/PySide6/Qt3DExtras/CMakeLists.txt b/sources/pyside6/PySide6/Qt3DExtras/CMakeLists.txt index cbda5068..6f6f637f 100644 --- a/sources/pyside6/PySide6/Qt3DExtras/CMakeLists.txt +++ b/sources/pyside6/PySide6/Qt3DExtras/CMakeLists.txt @@ -60,12 +60,14 @@ set(Qt3DExtras_include_dirs ${pyside6_SOURCE_DIR} ${Qt${QT_MAJOR_VERSION}Core_INCLUDE_DIRS} ${Qt${QT_MAJOR_VERSION}Gui_INCLUDE_DIRS} + ${Qt${QT_MAJOR_VERSION}OpenGL_INCLUDE_DIRS} ${Qt${QT_MAJOR_VERSION}3DCore_INCLUDE_DIRS} ${Qt${QT_MAJOR_VERSION}3DRender_INCLUDE_DIRS} ${Qt${QT_MAJOR_VERSION}3DExtras_INCLUDE_DIRS} ${libpyside_SOURCE_DIR} ${QtCore_GEN_DIR} ${QtGui_GEN_DIR} + ${QtOpenGL_GEN_DIR} ${Qt3DCore_GEN_DIR} ${Qt3DRender_GEN_DIR}) diff --git a/sources/pyside6/PySide6/Qt3DExtras/typesystem_3dextras.xml b/sources/pyside6/PySide6/Qt3DExtras/typesystem_3dextras.xml index 009dbdc9..d281ae15 100644 --- a/sources/pyside6/PySide6/Qt3DExtras/typesystem_3dextras.xml +++ b/sources/pyside6/PySide6/Qt3DExtras/typesystem_3dextras.xml @@ -4,7 +4,8 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + diff --git a/sources/pyside6/PySide6/Qt3DInput/typesystem_3dinput.xml b/sources/pyside6/PySide6/Qt3DInput/typesystem_3dinput.xml index 96160aee..fbdfc596 100644 --- a/sources/pyside6/PySide6/Qt3DInput/typesystem_3dinput.xml +++ b/sources/pyside6/PySide6/Qt3DInput/typesystem_3dinput.xml @@ -4,7 +4,8 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + diff --git a/sources/pyside6/PySide6/Qt3DLogic/typesystem_3dlogic.xml b/sources/pyside6/PySide6/Qt3DLogic/typesystem_3dlogic.xml index eef763e8..60f36253 100644 --- a/sources/pyside6/PySide6/Qt3DLogic/typesystem_3dlogic.xml +++ b/sources/pyside6/PySide6/Qt3DLogic/typesystem_3dlogic.xml @@ -4,7 +4,8 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + diff --git a/sources/pyside6/PySide6/Qt3DRender/CMakeLists.txt b/sources/pyside6/PySide6/Qt3DRender/CMakeLists.txt index a4391fef..72f85930 100644 --- a/sources/pyside6/PySide6/Qt3DRender/CMakeLists.txt +++ b/sources/pyside6/PySide6/Qt3DRender/CMakeLists.txt @@ -3,6 +3,8 @@ project(Qt3DRender) +set(Qt3DRender_DROPPED_ENTRIES) + set(Qt3DRender_SRC ${Qt3DRender_GEN_DIR}/qsharedpointer_propertyreaderinterface_wrapper.cpp ${Qt3DRender_GEN_DIR}/qsharedpointer_qtextureimagedata_wrapper.cpp @@ -26,6 +28,7 @@ ${Qt3DRender_GEN_DIR}/qt3drender_qclipplane_wrapper.cpp ${Qt3DRender_GEN_DIR}/qt3drender_qcolormask_wrapper.cpp ${Qt3DRender_GEN_DIR}/qt3drender_qcomputecommand_wrapper.cpp ${Qt3DRender_GEN_DIR}/qt3drender_qcullface_wrapper.cpp +${Qt3DRender_GEN_DIR}/qt3drender_qdebugoverlay_wrapper.cpp ${Qt3DRender_GEN_DIR}/qt3drender_qdepthrange_wrapper.cpp ${Qt3DRender_GEN_DIR}/qt3drender_qdepthtest_wrapper.cpp ${Qt3DRender_GEN_DIR}/qt3drender_qdirectionallight_wrapper.cpp @@ -134,13 +137,16 @@ set(Qt3DRender_include_dirs ${libpyside_SOURCE_DIR} ${QtCore_GEN_DIR} ${QtGui_GEN_DIR} - ${QtOpenGL_GEN_DIR} + ${Qt3DCore_GEN_DIR}) set(Qt3DRender_libraries pyside6 ${Qt${QT_MAJOR_VERSION}3DRender_LIBRARIES}) -set(Qt3DRender_deps Qt3DCore QtOpenGL) +set(Qt3DRender_deps Qt3DCore) + +check_qt_opengl("3DRender" Qt3DRender_include_dirs Qt3DRender_deps + Qt3DRender_DROPPED_ENTRIES) create_pyside_module(NAME Qt3DRender INCLUDE_DIRS Qt3DRender_include_dirs @@ -148,4 +154,5 @@ create_pyside_module(NAME Qt3DRender DEPS Qt3DRender_deps TYPESYSTEM_PATH Qt3DRender_SOURCE_DIR SOURCES Qt3DRender_SRC - TYPESYSTEM_NAME ${Qt3DRender_BINARY_DIR}/typesystem_3drender.xml) + TYPESYSTEM_NAME ${Qt3DRender_BINARY_DIR}/typesystem_3drender.xml + DROPPED_ENTRIES Qt3DRender_DROPPED_ENTRIES) diff --git a/sources/pyside6/PySide6/Qt3DRender/typesystem_3drender.xml b/sources/pyside6/PySide6/Qt3DRender/typesystem_3drender.xml index 3ebb0936..66fd7092 100644 --- a/sources/pyside6/PySide6/Qt3DRender/typesystem_3drender.xml +++ b/sources/pyside6/PySide6/Qt3DRender/typesystem_3drender.xml @@ -4,8 +4,14 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + + + + + + @@ -29,9 +35,7 @@ - - - + @@ -64,6 +68,7 @@ + @@ -222,9 +227,7 @@ - - - + diff --git a/sources/pyside6/PySide6/QtAsyncio/__init__.py b/sources/pyside6/PySide6/QtAsyncio/__init__.py index 3830ad88..60d1846d 100644 --- a/sources/pyside6/PySide6/QtAsyncio/__init__.py +++ b/sources/pyside6/PySide6/QtAsyncio/__init__.py @@ -20,19 +20,25 @@ __all__ = [ def run(coro: typing.Optional[typing.Coroutine] = None, keep_running: bool = True, quit_qapp: bool = True, *, - debug: typing.Optional[bool] = None) -> None: + handle_sigint: bool = False, + debug: typing.Optional[bool] = None) -> typing.Any: """Run the QtAsyncio event loop.""" # Event loop policies are expected to be deprecated with Python 3.13, with # subsequent removal in Python 3.15. At that point, part of the current # logic of the QAsyncioEventLoopPolicy constructor will have to be moved # here and/or to a loop factory class (to be provided as an argument to - # asyncio.run()), namely setting up the QCoreApplication and the SIGINT - # handler. + # asyncio.run()). In particular, this concerns the logic of setting up the + # QCoreApplication and the SIGINT handler. # # More details: # https://discuss.python.org/t/removing-the-asyncio-policy-system-asyncio-set-event-loop-policy-in-python-3-15/37553 # noqa: E501 - asyncio.set_event_loop_policy(QAsyncioEventLoopPolicy(quit_qapp=quit_qapp)) + default_policy = asyncio.get_event_loop_policy() + asyncio.set_event_loop_policy( + QAsyncioEventLoopPolicy(quit_qapp=quit_qapp, handle_sigint=handle_sigint)) + + ret = None + exc = None if keep_running: if coro: @@ -40,8 +46,15 @@ def run(coro: typing.Optional[typing.Coroutine] = None, asyncio.get_event_loop().run_forever() else: if coro: - asyncio.run(coro, debug=debug) + ret = asyncio.run(coro, debug=debug) else: - raise RuntimeError( + exc = RuntimeError( "QtAsyncio was set to keep running after the coroutine " "finished, but no coroutine was provided.") + + asyncio.set_event_loop_policy(default_policy) + + if ret: + return ret + if exc: + raise exc diff --git a/sources/pyside6/PySide6/QtAsyncio/events.py b/sources/pyside6/PySide6/QtAsyncio/events.py index aa89e984..86b89014 100644 --- a/sources/pyside6/PySide6/QtAsyncio/events.py +++ b/sources/pyside6/PySide6/QtAsyncio/events.py @@ -26,6 +26,19 @@ __all__ = [ class QAsyncioExecutorWrapper(QObject): + """ + Executors in asyncio allow running synchronous code in a separate thread or + process without blocking the event loop or interrupting the asynchronous + program flow. Callables are scheduled for execution by calling submit() or + map() on an executor object. + + Executors require a bit of extra work for QtAsyncio, as we can't use + naked Python threads; instead, we must make sure that the thread created + by executor.submit() has an event loop. This is achieved by not submitting + the callable directly, but a small wrapper that attaches a QEventLoop to + the executor thread, and then creates a zero-delay singleshot timer to push + the actual callable for the executor into this new event loop. + """ def __init__(self, func: typing.Callable, *args: typing.Tuple) -> None: super().__init__() @@ -37,16 +50,21 @@ class QAsyncioExecutorWrapper(QObject): def _cb(self): try: + # Call the synchronous callable that we submitted with submit() or + # map(). self._result = self._func(*self._args) except BaseException as e: self._exception = e self._loop.exit() def do(self): - # This creates a new event loop and dispatcher for the thread, if not already created. + # This creates a new event loop and dispatcher for the thread, if not + # already created. self._loop = QEventLoop() asyncio.events._set_running_loop(self._loop) + QTimer.singleShot(0, self._loop, lambda: self._cb()) + self._loop.exec() if self._exception is not None: raise self._exception @@ -57,9 +75,21 @@ class QAsyncioExecutorWrapper(QObject): class QAsyncioEventLoopPolicy(asyncio.AbstractEventLoopPolicy): + """ + Event loop policies are expected to be deprecated with Python 3.13, with + subsequent removal in Python 3.15. At that point, part of the current + logic of the QAsyncioEventLoopPolicy constructor will have to be moved + to QtAsyncio.run() and/or to a loop factory class (to be provided as an + argument to asyncio.run()). In particular, this concerns the logic of + setting up the QCoreApplication and the SIGINT handler. + + More details: + https://discuss.python.org/t/removing-the-asyncio-policy-system-asyncio-set-event-loop-policy-in-python-3-15/37553 + """ def __init__(self, application: typing.Optional[QCoreApplication] = None, - quit_qapp: bool = True) -> None: + quit_qapp: bool = True, + handle_sigint: bool = False) -> None: super().__init__() if application is None: if QCoreApplication.instance() is None: @@ -67,14 +97,22 @@ class QAsyncioEventLoopPolicy(asyncio.AbstractEventLoopPolicy): else: application = QCoreApplication.instance() self._application: QCoreApplication = application # type: ignore[assignment] + + # Configure whether the QCoreApplication at the core of QtAsyncio + # should be shut down when asyncio finishes. A special case where one + # would want to disable this is test suites that want to reuse a single + # QCoreApplication instance across all unit tests, which would fail if + # this instance is shut down every time. self._quit_qapp = quit_qapp + self._event_loop: typing.Optional[asyncio.AbstractEventLoop] = None - signal.signal(signal.SIGINT, signal.SIG_DFL) + if handle_sigint: + signal.signal(signal.SIGINT, signal.SIG_DFL) def get_event_loop(self) -> asyncio.AbstractEventLoop: if self._event_loop is None: - self._event_loop = QAsyncioEventLoop(self._application) + self._event_loop = QAsyncioEventLoop(self._application, quit_qapp=self._quit_qapp) return self._event_loop def set_event_loop(self, loop: typing.Optional[asyncio.AbstractEventLoop]) -> None: @@ -83,10 +121,10 @@ class QAsyncioEventLoopPolicy(asyncio.AbstractEventLoopPolicy): def new_event_loop(self) -> asyncio.AbstractEventLoop: return QAsyncioEventLoop(self._application, quit_qapp=self._quit_qapp) - def get_child_watcher(self) -> asyncio.AbstractChildWatcher: + def get_child_watcher(self) -> "asyncio.AbstractChildWatcher": raise DeprecationWarning("Child watchers are deprecated since Python 3.12") - def set_child_watcher(self, watcher: asyncio.AbstractChildWatcher) -> None: + def set_child_watcher(self, watcher: "asyncio.AbstractChildWatcher") -> None: raise DeprecationWarning("Child watchers are deprecated since Python 3.12") @@ -97,6 +135,14 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject): """ class ShutDownThread(QThread): + """ + Used to shut down the default executor when calling + shutdown_default_executor(). As the executor is a ThreadPoolExecutor, + it must be shut down in a separate thread as all the threads from the + thread pool must join, which we want to do without blocking the event + loop. + """ + def __init__(self, future: futures.QAsyncioFuture, loop: "QAsyncioEventLoop") -> None: super().__init__() self._future = future @@ -121,22 +167,48 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject): QObject.__init__(self) self._application: QCoreApplication = application + + # Configure whether the QCoreApplication at the core of QtAsyncio + # should be shut down when asyncio finishes. A special case where one + # would want to disable this is test suites that want to reuse a single + # QCoreApplication instance across all unit tests, which would fail if + # this instance is shut down every time. self._quit_qapp = quit_qapp + self._thread = QThread.currentThread() self._closed = False + # These two flags are used to determine whether the loop was stopped + # from inside the loop (i.e., coroutine or callback called stop()) or + # from outside the loop (i.e., the QApplication is being shut down, for + # example, by the user closing the window or by calling + # QApplication.quit()). The different cases can trigger slightly + # different behaviors (see the comments where the flags are used). + # There are two variables for this as in a third case the loop is still + # running and both flags are False. self._quit_from_inside = False self._quit_from_outside = False + # A set of all asynchronous generators that are currently running. self._asyncgens: typing.Set[collections.abc.AsyncGenerator] = set() # Starting with Python 3.11, this must be an instance of # ThreadPoolExecutor. self._default_executor = concurrent.futures.ThreadPoolExecutor() + # The exception handler, if set with set_exception_handler(). The + # exception handler is currently called in two places: One, if an + # asynchonrous generator raises an exception when closed, and two, if + # an exception is raised during the execution of a task. Currently, the + # default exception handler just prints the exception to the console. self._exception_handler: typing.Optional[typing.Callable] = self.default_exception_handler + + # The task factory, if set with set_task_factory(). Otherwise, a new + # task is created with the QAsyncioTask constructor. self._task_factory: typing.Optional[typing.Callable] = None + + # The future that is currently being awaited with run_until_complete(). self._future_to_complete: typing.Optional[futures.QAsyncioFuture] = None self._debug = bool(os.getenv("PYTHONASYNCIODEBUG", False)) @@ -146,6 +218,10 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject): # Running and stopping the loop def _run_until_complete_cb(self, future: futures.QAsyncioFuture) -> None: + """ + A callback that stops the loop when the future is done, used when + running the loop with run_until_complete(). + """ if not future.cancelled(): if isinstance(future.exception(), (SystemExit, KeyboardInterrupt)): return @@ -186,16 +262,29 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject): asyncio.events._set_running_loop(None) def _about_to_quit_cb(self): + """ A callback for the aboutToQuit signal of the QCoreApplication. """ if not self._quit_from_inside: + # If the aboutToQuit signal is emitted, the user is closing the + # application window or calling QApplication.quit(). In this case, + # we want to close the event loop, and we consider this a quit from + # outside the loop. self._quit_from_outside = True + self.close() def stop(self) -> None: if self._future_to_complete is not None: if self._future_to_complete.done(): self._future_to_complete = None else: + # Do not stop the loop if there is a future still being awaited + # with run_until_complete(). return + self._quit_from_inside = True + + # The user might want to keep the QApplication running after the event + # event loop finishes, which they can control with the quit_qapp + # argument. if self._quit_qapp: self._application.quit() @@ -206,14 +295,12 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject): return self._closed def close(self) -> None: - if self.is_running(): + if self.is_running() and not self._quit_from_outside: raise RuntimeError("Cannot close a running event loop") if self.is_closed(): return if self._default_executor is not None: self._default_executor.shutdown(wait=False) - if self._quit_qapp: - self._application.shutdown() self._closed = True async def shutdown_asyncgens(self) -> None: @@ -273,6 +360,8 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject): def call_soon_threadsafe(self, callback: typing.Callable, *args: typing.Any, context: typing.Optional[contextvars.Context] = None) -> asyncio.Handle: + if self.is_closed(): + raise RuntimeError("Event loop is closed") if context is None: context = contextvars.copy_context() return self._call_soon_impl(callback, *args, context=context, is_threadsafe=True) @@ -295,10 +384,9 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject): callback: typing.Callable, *args: typing.Any, context: typing.Optional[contextvars.Context] = None, is_threadsafe: typing.Optional[bool] = False) -> asyncio.TimerHandle: + """ All call_at() and call_later() methods map to this method. """ if not isinstance(when, (int, float)): raise TypeError("when must be an int or float") - if self.is_closed(): - raise RuntimeError("Event loop is closed") return QAsyncioTimerHandle(when, callback, args, self, context, is_threadsafe=is_threadsafe) def call_at(self, when: typing.Union[int, float], @@ -318,8 +406,6 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject): coro: typing.Union[collections.abc.Generator, collections.abc.Coroutine], *, name: typing.Optional[str] = None, context: typing.Optional[contextvars.Context] = None) -> tasks.QAsyncioTask: - if self.is_closed(): - raise RuntimeError("Event loop is closed") if self._task_factory is None: task = tasks.QAsyncioTask(coro, loop=self, name=name, context=context) else: @@ -483,6 +569,13 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject): raise RuntimeError("Event loop is closed") if executor is None: executor = self._default_executor + + # Executors require a bit of extra work for QtAsyncio, as we can't use + # naked Python threads; instead, we must make sure that the thread + # created by executor.submit() has an event loop. This is achieved by + # not submitting the callable directly, but a small wrapper that + # attaches a QEventLoop to the executor thread, and then pushes the + # actual callable for the executor into this new event loop. wrapper = QAsyncioExecutorWrapper(func, *args) return asyncio.futures.wrap_future( executor.submit(wrapper.do), loop=self @@ -542,6 +635,12 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject): class QAsyncioHandle(): + """ + The handle enqueues a callback to be executed by the event loop, and allows + for this callback to be cancelled before it is executed. This callback will + typically execute the step function for a task. This makes the handle one + of the main components of asyncio. + """ class HandleState(enum.Enum): PENDING = enum.auto() CANCELLED = enum.auto() @@ -561,18 +660,30 @@ class QAsyncioHandle(): self._state = QAsyncioHandle.HandleState.PENDING self._start() + def _start(self) -> None: + self._schedule_event(self._timeout, lambda: self._cb()) + def _schedule_event(self, timeout: int, func: typing.Callable) -> None: + # Do not schedule events from asyncio when the app is quit from outside + # the event loop, as this would cause events to be enqueued after the + # event loop was destroyed. if not self._loop.is_closed() and not self._loop._quit_from_outside: if self._is_threadsafe: + # This singleShot overload will push func into self._loop + # instead of the current thread's loop. This allows scheduling + # a callback from a different thread, which is necessary for + # thread-safety. + # https://docs.python.org/3/library/asyncio-dev.html#asyncio-multithreading QTimer.singleShot(timeout, self._loop, func) else: QTimer.singleShot(timeout, func) - def _start(self) -> None: - self._schedule_event(self._timeout, lambda: self._cb()) - @Slot() def _cb(self) -> None: + """ + A slot, enqueued into the event loop, that wraps around the actual + callback, typically the step function of a task. + """ if self._state == QAsyncioHandle.HandleState.PENDING: if self._context is not None: self._context.run(self._callback, *self._args) @@ -582,7 +693,9 @@ class QAsyncioHandle(): def cancel(self) -> None: if self._state == QAsyncioHandle.HandleState.PENDING: - # The old timer that was created in _start will still trigger but _cb won't do anything. + # The old timer that was created in _start will still trigger but + # _cb won't do anything, therefore the callback is effectively + # cancelled. self._state = QAsyncioHandle.HandleState.CANCELLED def cancelled(self) -> bool: @@ -596,13 +709,16 @@ class QAsyncioTimerHandle(QAsyncioHandle, asyncio.TimerHandle): QAsyncioHandle.__init__(self, callback, args, loop, context, is_threadsafe) self._when = when - self._timeout = int(max(self._when - self._loop.time(), 0) * 1000) + time = self._loop.time() + self._timeout = round(max(self._when - time, 0) * 1000) QAsyncioHandle._start(self) - # Override this so that timer.start() is only called once at the end - # of the constructor for both QtHandle and QtTimerHandle. def _start(self) -> None: + """ + Overridden so that timer.start() is only called once at the end of the + constructor for both QtHandle and QtTimerHandle. + """ pass def when(self) -> float: diff --git a/sources/pyside6/PySide6/QtAsyncio/futures.py b/sources/pyside6/PySide6/QtAsyncio/futures.py index 611bd563..cbb005fc 100644 --- a/sources/pyside6/PySide6/QtAsyncio/futures.py +++ b/sources/pyside6/PySide6/QtAsyncio/futures.py @@ -36,10 +36,11 @@ class QAsyncioFuture(): self._result: typing.Any = None self._exception: typing.Optional[BaseException] = None - self._callbacks: typing.List[typing.Callable] = list() - self._cancel_message: typing.Optional[str] = None + # List of callbacks that are called when the future is done. + self._callbacks: typing.List[typing.Callable] = list() + def __await__(self): if not self.done(): self._asyncio_future_blocking = True @@ -51,6 +52,7 @@ class QAsyncioFuture(): __iter__ = __await__ def _schedule_callbacks(self, context: typing.Optional[contextvars.Context] = None): + """ A future can optionally have callbacks that are called when the future is done. """ for cb in self._callbacks: self._loop.call_soon( cb, self, context=context if context else self._context) diff --git a/sources/pyside6/PySide6/QtAsyncio/tasks.py b/sources/pyside6/PySide6/QtAsyncio/tasks.py index bc3d41a7..7edc1509 100644 --- a/sources/pyside6/PySide6/QtAsyncio/tasks.py +++ b/sources/pyside6/PySide6/QtAsyncio/tasks.py @@ -20,16 +20,22 @@ class QAsyncioTask(futures.QAsyncioFuture): context: typing.Optional[contextvars.Context] = None) -> None: super().__init__(loop=loop, context=context) - self._coro = coro + self._coro = coro # The coroutine for which this task was created. self._name = name if name else "QtTask" + # The task creates a handle for its coroutine. The handle enqueues the + # task's step function as its callback in the event loop. self._handle = self._loop.call_soon(self._step, context=self._context) - self._cancellation_requests = 0 - + # The task step function executes the coroutine until it finishes, + # raises an exception or returns a future. If a future was returned, + # the task will await its completion (or exception). self._future_to_await: typing.Optional[asyncio.Future] = None + + self._cancelled = False self._cancel_message: typing.Optional[str] = None + # https://docs.python.org/3/library/asyncio-extending.html#task-lifetime-support asyncio._register_task(self) # type: ignore[arg-type] def __repr__(self) -> str: @@ -58,24 +64,39 @@ class QAsyncioTask(futures.QAsyncioFuture): def _step(self, exception_or_future: typing.Union[ BaseException, futures.QAsyncioFuture, None] = None) -> None: + """ + The step function is the heart of a task. It is scheduled in the event + loop repeatedly, executing the coroutine "step" by "step" (i.e., + iterating through the asynchronous generator) until it finishes with an + exception or successfully. Each step can optionally receive an + exception or a future as a result from a previous step to handle. + """ + if self.done(): return result = None self._future_to_await = None + if asyncio.futures.isfuture(exception_or_future): + try: + exception_or_future.result() + except BaseException as e: + exception_or_future = e + try: asyncio._enter_task(self._loop, self) # type: ignore[arg-type] - if exception_or_future is None: - result = self._coro.send(None) - elif asyncio.futures.isfuture(exception_or_future): - try: - exception_or_future.result() - except BaseException as e: - result = self._coro.throw(e) - else: - result = self._coro.send(None) - elif isinstance(exception_or_future, BaseException): + + # It is at this point that the coroutine is resumed for the current + # step (i.e. asynchronous generator iteration). It will now be + # executed until it yields (and potentially returns a future), + # raises an exception, is cancelled, or finishes successfully. + + if isinstance(exception_or_future, BaseException): + # If the coroutine doesn't handle this exception, it propagates + # to the caller. result = self._coro.throw(exception_or_future) + else: + result = self._coro.send(None) except StopIteration as e: self._state = futures.QAsyncioFuture.FutureState.DONE_WITH_RESULT self._result = e.value @@ -87,16 +108,32 @@ class QAsyncioTask(futures.QAsyncioFuture): self._exception = e else: if asyncio.futures.isfuture(result): + # If the coroutine yields a future, the task will await its + # completion, and at that point the step function will be + # called again. result.add_done_callback( self._step, context=self._context) # type: ignore[arg-type] self._future_to_await = result + if self._cancelled: + # If the task was cancelled, then a new future should be + # cancelled as well. Otherwise, in some scenarios like + # a loop inside the task and with bad timing, if the new + # future is not cancelled, the task would continue running + # in this loop despite having been cancelled. This bad + # timing can occur especially if the first future finishes + # very quickly. + self._future_to_await.cancel(self._cancel_message) elif result is None: + # If no future was yielded, we schedule the step function again + # without any arguments. self._loop.call_soon(self._step, context=self._context) else: + # This is not supposed to happen. exception = RuntimeError(f"Bad task result: {result}") self._loop.call_soon(self._step, exception, context=self._context) finally: asyncio._leave_task(self._loop, self) # type: ignore[arg-type] + if self._exception: self._loop.call_exception_handler({ "message": (str(self._exception) if self._exception @@ -108,8 +145,11 @@ class QAsyncioTask(futures.QAsyncioFuture): if asyncio.futures.isfuture(exception_or_future) else None) }) + if self.done(): self._schedule_callbacks() + + # https://docs.python.org/3/library/asyncio-extending.html#task-lifetime-support asyncio._unregister_task(self) # type: ignore[arg-type] def get_stack(self, *, limit=None) -> typing.List[typing.Any]: @@ -135,7 +175,10 @@ class QAsyncioTask(futures.QAsyncioFuture): self._cancel_message = msg self._handle.cancel() if self._future_to_await is not None: + # A task that is awaiting a future must also cancel this future in + # order for the cancellation to be successful. self._future_to_await.cancel(msg) + self._cancelled = True return True def uncancel(self) -> None: diff --git a/sources/pyside6/PySide6/QtAxContainer/typesystem_axcontainer.xml b/sources/pyside6/PySide6/QtAxContainer/typesystem_axcontainer.xml index 4c570f87..4d1cbe91 100644 --- a/sources/pyside6/PySide6/QtAxContainer/typesystem_axcontainer.xml +++ b/sources/pyside6/PySide6/QtAxContainer/typesystem_axcontainer.xml @@ -4,7 +4,8 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + diff --git a/sources/pyside6/PySide6/QtBluetooth/typesystem_bluetooth.xml b/sources/pyside6/PySide6/QtBluetooth/typesystem_bluetooth.xml index 737305bb..a3687b6b 100644 --- a/sources/pyside6/PySide6/QtBluetooth/typesystem_bluetooth.xml +++ b/sources/pyside6/PySide6/QtBluetooth/typesystem_bluetooth.xml @@ -3,7 +3,8 @@ // Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + diff --git a/sources/pyside6/PySide6/QtCharts/CMakeLists.txt b/sources/pyside6/PySide6/QtCharts/CMakeLists.txt index f7a4d3cc..20126fde 100644 --- a/sources/pyside6/PySide6/QtCharts/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtCharts/CMakeLists.txt @@ -73,10 +73,7 @@ set(QtCharts_include_dirs ${QtCharts_SOURCE_DIR} ${QtWidgets_GEN_DIR}) set(QtCharts_libraries pyside6 - ${Qt${QT_MAJOR_VERSION}Charts_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Core_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Gui_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Widgets_LIBRARIES}) + ${Qt${QT_MAJOR_VERSION}Charts_LIBRARIES}) set(QtCharts_deps QtCore QtGui QtWidgets) diff --git a/sources/pyside6/PySide6/QtCharts/typesystem_charts.xml b/sources/pyside6/PySide6/QtCharts/typesystem_charts.xml index a9cd7317..d4337df8 100644 --- a/sources/pyside6/PySide6/QtCharts/typesystem_charts.xml +++ b/sources/pyside6/PySide6/QtCharts/typesystem_charts.xml @@ -3,7 +3,8 @@ // Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + diff --git a/sources/pyside6/PySide6/QtConcurrent/CMakeLists.txt b/sources/pyside6/PySide6/QtConcurrent/CMakeLists.txt index c32ff17f..134e44ed 100644 --- a/sources/pyside6/PySide6/QtConcurrent/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtConcurrent/CMakeLists.txt @@ -18,12 +18,11 @@ set(QtConcurrent_include_dirs ${QtConcurrent_SOURCE_DIR} ${Qt${QT_MAJOR_VERSION}Core_INCLUDE_DIRS} ${Qt${QT_MAJOR_VERSION}Concurrent_INCLUDE_DIRS} ${libpyside_SOURCE_DIR} - ${QtCore_GEN_DIR} - ) -set(QtConcurrent_libraries pyside6 - ${QtConcurrent_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Core_LIBRARIES} - ) + ${QtCore_GEN_DIR}) + +set(QtConcurrent_libraries pyside6 + ${Qt${QT_MAJOR_VERSION}Concurrent_LIBRARIES}) + set(QtConcurrent_deps QtCore) create_pyside_module(NAME QtConcurrent diff --git a/sources/pyside6/PySide6/QtConcurrent/typesystem_concurrent.xml b/sources/pyside6/PySide6/QtConcurrent/typesystem_concurrent.xml index 84213a61..1892793b 100644 --- a/sources/pyside6/PySide6/QtConcurrent/typesystem_concurrent.xml +++ b/sources/pyside6/PySide6/QtConcurrent/typesystem_concurrent.xml @@ -3,7 +3,8 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + - + diff --git a/sources/pyside6/PySide6/QtCore/typesystem_core_common.xml b/sources/pyside6/PySide6/QtCore/typesystem_core_common.xml index 5168373f..19a50848 100644 --- a/sources/pyside6/PySide6/QtCore/typesystem_core_common.xml +++ b/sources/pyside6/PySide6/QtCore/typesystem_core_common.xml @@ -36,16 +36,35 @@ - - - - - - + + + + + + + + + + + + + + + + + + + + + + + @@ -138,10 +157,10 @@ - - - - + + + + @@ -233,6 +252,17 @@ + + + + + + + + + + @@ -252,6 +282,9 @@ + + + @@ -620,6 +653,8 @@ + @@ -654,6 +689,8 @@ + @@ -666,7 +703,8 @@ - + @@ -776,10 +814,7 @@ - - - - + @@ -837,13 +872,11 @@ - - - - + + @@ -949,10 +982,7 @@ - - - - + @@ -970,7 +1000,6 @@ - @@ -1019,10 +1048,7 @@ - - - - + @@ -1039,7 +1065,6 @@ - @@ -1153,10 +1178,7 @@ - - - - + @@ -1174,7 +1196,6 @@ - @@ -1224,10 +1245,7 @@ - - - - + @@ -1271,6 +1289,7 @@ qRegisterMetaType<QList<QPersistentModelIndex> >("QList_QPersistentModelIndex"); + @@ -1320,6 +1339,7 @@ + @@ -1368,11 +1388,21 @@ - + + + + + + + + + + + @@ -1500,6 +1530,16 @@ + + + + + + + + + @@ -1638,8 +1678,9 @@ - + tag in Qt6? + PYSIDE-2756: The return-type attribute is unnecessary --> + @@ -1656,7 +1697,8 @@ - + @@ -1693,7 +1735,9 @@ - + + + @@ -1730,14 +1774,23 @@ - + + - + - + @@ -1745,20 +1798,20 @@ + return-type="QMetaObject::Connection" static="yes" overload-number="3"> + return-type="QMetaObject::Connection" static="yes" overload-number="4"> + return-type="QMetaObject::Connection" overload-number="5"> + return-type="QMetaObject::Connection" overload-number="6"> @@ -1849,7 +1902,8 @@ - + @@ -1857,7 +1911,7 @@ - + @@ -1867,7 +1921,6 @@ - @@ -2373,8 +2426,8 @@ + - @@ -2423,6 +2476,7 @@ + @@ -2500,6 +2554,11 @@ + + + + + @@ -2523,11 +2582,13 @@ + - + - - + + @@ -2585,20 +2646,17 @@ - - -.. class:: QCoreApplication(args) - - Constructs a Qt kernel application. Kernel applications are applications - without a graphical user interface. These type of applications are used - at the console or as server processes. - - The *args* argument is processed by the application, and made available - in a more convenient form by the :meth:`~QCoreApplication.arguments()` - method. - - + + + Constructs a Qt kernel application. Kernel applications are applications + without a graphical user interface. These type of applications are used + at the console or as server processes. + + The *args* argument is processed by the application, and made available + in a more convenient form by the :meth:`~PySide6.QtCore.QCoreApplication.arguments()` + method. + @@ -2717,15 +2775,18 @@ file="../glue/qtcore.cpp" snippet="repr-qevent"/> - + - - + + @@ -2793,7 +2854,7 @@ - + @@ -2804,7 +2865,7 @@ - + @@ -2886,7 +2947,7 @@ - + @@ -2895,8 +2956,9 @@ + - + @@ -3048,6 +3110,7 @@ + @@ -3557,11 +3620,12 @@ - - - - - + + + + + + diff --git a/sources/pyside6/PySide6/QtDBus/typesystem_dbus.xml b/sources/pyside6/PySide6/QtDBus/typesystem_dbus.xml index 02b26d7e..ebed2225 100644 --- a/sources/pyside6/PySide6/QtDBus/typesystem_dbus.xml +++ b/sources/pyside6/PySide6/QtDBus/typesystem_dbus.xml @@ -3,7 +3,8 @@ // Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + @@ -84,7 +85,12 @@ - + + + DBus signals can be captured with string-based connections + (see :ref:`signals-and-slots-strings`). + + diff --git a/sources/pyside6/PySide6/QtDataVisualization/CMakeLists.txt b/sources/pyside6/PySide6/QtDataVisualization/CMakeLists.txt index b7ec9e4e..1276b424 100644 --- a/sources/pyside6/PySide6/QtDataVisualization/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtDataVisualization/CMakeLists.txt @@ -54,10 +54,8 @@ set(QtDataVisualization_include_dirs ${QtDataVisualization_SOURCE_DIR} ${QtCore_GEN_DIR} ${QtGui_GEN_DIR}) -set(QtDataVisualization_libraries pyside6 - ${Qt${QT_MAJOR_VERSION}DataVisualization_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Core_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Gui_LIBRARIES}) +set(QtDataVisualization_libraries pyside6 + ${Qt${QT_MAJOR_VERSION}DataVisualization_LIBRARIES}) set(QtDataVisualization_deps QtCore QtGui) diff --git a/sources/pyside6/PySide6/QtDataVisualization/typesystem_datavisualization.xml b/sources/pyside6/PySide6/QtDataVisualization/typesystem_datavisualization.xml index 3df435c4..70014e3d 100644 --- a/sources/pyside6/PySide6/QtDataVisualization/typesystem_datavisualization.xml +++ b/sources/pyside6/PySide6/QtDataVisualization/typesystem_datavisualization.xml @@ -3,7 +3,8 @@ // Copyright (C) 2017 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + diff --git a/sources/pyside6/PySide6/QtDesigner/CMakeLists.txt b/sources/pyside6/PySide6/QtDesigner/CMakeLists.txt index 20be3a7d..e91532b8 100644 --- a/sources/pyside6/PySide6/QtDesigner/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtDesigner/CMakeLists.txt @@ -51,14 +51,11 @@ set(QtDesigner_include_dirs ${QtDesigner_SOURCE_DIR} ${QtDesigner_BINARY_DIR} ${QtCore_GEN_DIR} ${QtGui_GEN_DIR} - ${QtWidgets_GEN_DIR} - ) + ${QtWidgets_GEN_DIR}) + set(QtDesigner_libraries pyside6 - ${Qt${QT_MAJOR_VERSION}Core_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Gui_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Widgets_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Designer_LIBRARIES} - ) + ${Qt${QT_MAJOR_VERSION}Designer_LIBRARIES}) + set(QtDesigner_deps QtWidgets) create_pyside_module(NAME QtDesigner diff --git a/sources/pyside6/PySide6/QtDesigner/qpydesignercustomwidgetcollection.cpp b/sources/pyside6/PySide6/QtDesigner/qpydesignercustomwidgetcollection.cpp index 22b76e13..31f8ec15 100644 --- a/sources/pyside6/PySide6/QtDesigner/qpydesignercustomwidgetcollection.cpp +++ b/sources/pyside6/PySide6/QtDesigner/qpydesignercustomwidgetcollection.cpp @@ -9,6 +9,8 @@ #include #include +QT_BEGIN_NAMESPACE + static QString pyStringToQString(PyObject *s) { const char *utf8 = _PepUnicode_AsString(s); @@ -217,3 +219,5 @@ void QPyDesignerCustomWidgetCollection::addCustomWidget(QDesignerCustomWidgetInt { instance()->m_customWidgets.append(c); } + +QT_END_NAMESPACE diff --git a/sources/pyside6/PySide6/QtDesigner/typesystem_designer.xml b/sources/pyside6/PySide6/QtDesigner/typesystem_designer.xml index d82d681c..7d37cbdd 100644 --- a/sources/pyside6/PySide6/QtDesigner/typesystem_designer.xml +++ b/sources/pyside6/PySide6/QtDesigner/typesystem_designer.xml @@ -3,7 +3,8 @@ // Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + diff --git a/sources/pyside6/PySide6/QtGraphs/CMakeLists.txt b/sources/pyside6/PySide6/QtGraphs/CMakeLists.txt index 1fc47dc8..59cef220 100644 --- a/sources/pyside6/PySide6/QtGraphs/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtGraphs/CMakeLists.txt @@ -3,9 +3,12 @@ project(QtGraphs) +set(QtGraphs_DROPPED_ENTRIES) + list(APPEND QtGraphs_src "${QtGraphs_SOURCE_DIR}/qtgraphs_helper.cpp") set(QtGraphs_SRC +# 3D ${QtGraphs_GEN_DIR}/qabstract3daxis_wrapper.cpp ${QtGraphs_GEN_DIR}/qcategory3daxis_wrapper.cpp ${QtGraphs_GEN_DIR}/qlogvalue3daxisformatter_wrapper.cpp @@ -30,9 +33,6 @@ ${QtGraphs_GEN_DIR}/qsurface3dseries_wrapper.cpp ${QtGraphs_GEN_DIR}/qsurfacedataitem_wrapper.cpp ${QtGraphs_GEN_DIR}/qsurfacedataproxy_wrapper.cpp ${QtGraphs_GEN_DIR}/q3dbars_wrapper.cpp -${QtGraphs_GEN_DIR}/q3dcamera_wrapper.cpp -${QtGraphs_GEN_DIR}/q3dlight_wrapper.cpp -${QtGraphs_GEN_DIR}/q3dobject_wrapper.cpp ${QtGraphs_GEN_DIR}/q3dscatter_wrapper.cpp ${QtGraphs_GEN_DIR}/q3dscene_wrapper.cpp ${QtGraphs_GEN_DIR}/q3dsurface_wrapper.cpp @@ -41,6 +41,19 @@ ${QtGraphs_GEN_DIR}/q3dinputhandler_wrapper.cpp ${QtGraphs_GEN_DIR}/qabstract3dinputhandler_wrapper.cpp ${QtGraphs_GEN_DIR}/qtouch3dinputhandler_wrapper.cpp ${QtGraphs_GEN_DIR}/q3dtheme_wrapper.cpp +# 2D +${QtGraphs_GEN_DIR}/qbarcategoryaxis_wrapper.cpp +${QtGraphs_GEN_DIR}/qabstractaxis_wrapper.cpp +${QtGraphs_GEN_DIR}/qvalueaxis_wrapper.cpp +${QtGraphs_GEN_DIR}/qabstractbarseries_wrapper.cpp +${QtGraphs_GEN_DIR}/qbarseries_wrapper.cpp +${QtGraphs_GEN_DIR}/qbarset_wrapper.cpp +${QtGraphs_GEN_DIR}/qlineseries_wrapper.cpp +${QtGraphs_GEN_DIR}/qabstractseries_wrapper.cpp +${QtGraphs_GEN_DIR}/qscatterseries_wrapper.cpp +${QtGraphs_GEN_DIR}/qgraphtheme_wrapper.cpp +${QtGraphs_GEN_DIR}/qseriestheme_wrapper.cpp +${QtGraphs_GEN_DIR}/qxyseries_wrapper.cpp # module is always needed ${QtGraphs_GEN_DIR}/qtgraphs_module_wrapper.cpp ) @@ -52,7 +65,6 @@ set(QtGraphs_include_dirs ${QtGraphs_SOURCE_DIR} ${Qt${QT_MAJOR_VERSION}Gui_INCLUDE_DIRS} ${Qt${QT_MAJOR_VERSION}Widgets_INCLUDE_DIRS} ${Qt${QT_MAJOR_VERSION}Qml_INCLUDE_DIRS} - ${Qt${QT_MAJOR_VERSION}OpenGL_INCLUDE_DIRS} ${Qt${QT_MAJOR_VERSION}Quick_INCLUDE_DIRS} ${Qt${QT_MAJOR_VERSION}QuickWidgets_INCLUDE_DIRS} ${Qt${QT_MAJOR_VERSION}Graphs_INCLUDE_DIRS} @@ -63,24 +75,17 @@ set(QtGraphs_include_dirs ${QtGraphs_SOURCE_DIR} ${QtGui_GEN_DIR} ${QtWidgets_GEN_DIR} ${QtQml_GEN_DIR} - ${QtOpenGL_GEN_DIR} ${QtQuick_GEN_DIR} ${QtQuickWidgets_GEN_DIR} ${QtQuick3D_GEN_DIR}) set(QtGraphs_libraries pyside6 - ${Qt${QT_MAJOR_VERSION}Graphs_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Core_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Network_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Gui_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Widgets_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Qml_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}OpenGL_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Quick_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}QuickWidgets_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Quick3D_LIBRARIES}) + ${Qt${QT_MAJOR_VERSION}Graphs_LIBRARIES}) + +set(QtGraphs_deps QtCore QtNetwork QtGui QtWidgets QtQml QtQuick QtQuickWidgets QtQuick3D) -set(QtGraphs_deps QtCore QtNetwork QtGui QtWidgets QtQml QtOpenGL QtQuick QtQuickWidgets QtQuick3D) +check_qt_opengl("Graphs" QtGraphs_include_dirs QtGraphs_deps + QtGraphs_DROPPED_ENTRIES) create_pyside_module(NAME QtGraphs INCLUDE_DIRS QtGraphs_include_dirs @@ -88,7 +93,8 @@ create_pyside_module(NAME QtGraphs DEPS QtGraphs_deps TYPESYSTEM_PATH QtGraphs_SOURCE_DIR SOURCES QtGraphs_SRC - STATIC_SOURCES QtGraphs_src) + STATIC_SOURCES QtGraphs_src + DROPPED_ENTRIES QtGraphs_DROPPED_ENTRIES) install(FILES ${pyside6_SOURCE_DIR}/qtgraphs_helper.h DESTINATION include/PySide6/QtGraphs) diff --git a/sources/pyside6/PySide6/QtGraphs/qtgraphs_helper.cpp b/sources/pyside6/PySide6/QtGraphs/qtgraphs_helper.cpp index 05d6b00a..19fe4f81 100644 --- a/sources/pyside6/PySide6/QtGraphs/qtgraphs_helper.cpp +++ b/sources/pyside6/PySide6/QtGraphs/qtgraphs_helper.cpp @@ -18,28 +18,28 @@ static void populateArray(double xStart, double deltaX, double zStart, double de const qsizetype zStride = zStrideBytes / sizeof(T); double z = zStart; for (qsizetype zi = 0; zi < zSize; ++zi) { - auto *row = new QSurfaceDataRow; - row->reserve(xSize); - result->append(row); + QSurfaceDataRow row; + row.reserve(xSize); double x = xStart; auto *rowDataEnd = data + xSize; for (auto *d = data; d < rowDataEnd; ++d) { - row->append(QSurfaceDataItem(QVector3D(x, *d, z))); + row.append(QSurfaceDataItem(QVector3D(x, *d, z))); x += deltaX; } + result->append(row); data += zStride; z += deltaZ; } } -QSurfaceDataArray *surfaceDataFromNp(double xStart, double deltaX, double zStart, double deltaZ, - PyObject *pyData) +QSurfaceDataArray surfaceDataFromNp(double xStart, double deltaX, double zStart, double deltaZ, + PyObject *pyData) { static const char funcName[] = "QSurfaceDataProxy.resetArrayNp"; - auto *result = new QSurfaceDataArray; + QSurfaceDataArray result; auto view = Shiboken::Numpy::View::fromPyObject(pyData); if (!view) { @@ -59,35 +59,35 @@ QSurfaceDataArray *surfaceDataFromNp(double xStart, double deltaX, double zStart switch (view.type) { case Shiboken::Numpy::View::Int16: populateArray(xStart, deltaX, zStart, deltaZ, xSize, zSize, view.stride[0], - reinterpret_cast(view.data), result); + reinterpret_cast(view.data), &result); break; case Shiboken::Numpy::View::Unsigned16: populateArray(xStart, deltaX, zStart, deltaZ, xSize, zSize, view.stride[0], - reinterpret_cast(view.data), result); + reinterpret_cast(view.data), &result); break; case Shiboken::Numpy::View::Int: populateArray(xStart, deltaX, zStart, deltaZ, xSize, zSize, view.stride[0], - reinterpret_cast(view.data), result); + reinterpret_cast(view.data), &result); break; case Shiboken::Numpy::View::Unsigned: populateArray(xStart, deltaX, zStart, deltaZ, xSize, zSize, view.stride[0], - reinterpret_cast(view.data), result); + reinterpret_cast(view.data), &result); break; case Shiboken::Numpy::View::Int64: populateArray(xStart, deltaX, zStart, deltaZ, xSize, zSize, view.stride[0], - reinterpret_cast(view.data), result); + reinterpret_cast(view.data), &result); break; case Shiboken::Numpy::View::Unsigned64: populateArray(xStart, deltaX, zStart, deltaZ, xSize, zSize, view.stride[0], - reinterpret_cast(view.data), result); + reinterpret_cast(view.data), &result); break; case Shiboken::Numpy::View::Float: populateArray(xStart, deltaX, zStart, deltaZ, xSize, zSize, view.stride[0], - reinterpret_cast(view.data), result); + reinterpret_cast(view.data), &result); break; case Shiboken::Numpy::View::Double: populateArray(xStart, deltaX, zStart, deltaZ, xSize, zSize, view.stride[0], - reinterpret_cast(view.data), result); + reinterpret_cast(view.data), &result); break; } diff --git a/sources/pyside6/PySide6/QtGraphs/typesystem_graphs.xml b/sources/pyside6/PySide6/QtGraphs/typesystem_graphs.xml index eb893b2a..43e4458b 100644 --- a/sources/pyside6/PySide6/QtGraphs/typesystem_graphs.xml +++ b/sources/pyside6/PySide6/QtGraphs/typesystem_graphs.xml @@ -3,7 +3,8 @@ // Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + @@ -14,40 +15,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -63,37 +30,12 @@ - - - - - Sets the normalized grid line positions to ``grid_positions``. - - - - - - Sets the normalized label positions to ``label_positions``. - - - - - - Sets the label strings to ``label_strings``. - - - @@ -111,89 +53,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -221,42 +80,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -277,35 +100,6 @@ #include <sbknumpycheck.h> #include <qtgraphs_helper.h> - - - - - - - - - - - - - - - - - - - - - - - - @@ -340,11 +134,6 @@ - - - - - @@ -408,6 +197,7 @@ + @@ -461,6 +251,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sources/pyside6/PySide6/QtGui/CMakeLists.txt b/sources/pyside6/PySide6/QtGui/CMakeLists.txt index da150af7..99e0789d 100644 --- a/sources/pyside6/PySide6/QtGui/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtGui/CMakeLists.txt @@ -25,10 +25,9 @@ ${QtGui_GEN_DIR}/qrhicommandbuffer_wrapper.cpp ${QtGui_GEN_DIR}/qrhicomputepipeline_wrapper.cpp ${QtGui_GEN_DIR}/qrhidepthstencilclearvalue_wrapper.cpp ${QtGui_GEN_DIR}/qrhidriverinfo_wrapper.cpp -${QtGui_GEN_DIR}/qrhigles2initparams_wrapper.cpp -${QtGui_GEN_DIR}/qrhigles2nativehandles_wrapper.cpp ${QtGui_GEN_DIR}/qrhigraphicspipeline_targetblend_wrapper.cpp ${QtGui_GEN_DIR}/qrhigraphicspipeline_wrapper.cpp +${QtGui_GEN_DIR}/qrhigraphicspipeline_stencilopstate_wrapper.cpp ${QtGui_GEN_DIR}/qrhiinitparams_wrapper.cpp ${QtGui_GEN_DIR}/qrhinativehandles_wrapper.cpp ${QtGui_GEN_DIR}/qrhinullinitparams_wrapper.cpp @@ -89,6 +88,7 @@ ${QtGui_GEN_DIR}/qaccessibleactioninterface_wrapper.cpp ${QtGui_GEN_DIR}/qaccessibleeditabletextinterface_wrapper.cpp ${QtGui_GEN_DIR}/qaccessibleevent_wrapper.cpp ${QtGui_GEN_DIR}/qaccessibleobject_wrapper.cpp +${QtGui_GEN_DIR}/qaccessibleselectioninterface_wrapper.cpp ${QtGui_GEN_DIR}/qaccessiblestatechangeevent_wrapper.cpp ${QtGui_GEN_DIR}/qaccessibletablecellinterface_wrapper.cpp ${QtGui_GEN_DIR}/qaccessibletablemodelchangeevent_wrapper.cpp @@ -106,6 +106,7 @@ ${QtGui_GEN_DIR}/qactiongroup_wrapper.cpp ${QtGui_GEN_DIR}/qbackingstore_wrapper.cpp ${QtGui_GEN_DIR}/qbitmap_wrapper.cpp ${QtGui_GEN_DIR}/qbrush_wrapper.cpp +${QtGui_GEN_DIR}/qchildwindowevent_wrapper.cpp ${QtGui_GEN_DIR}/qclipboard_wrapper.cpp ${QtGui_GEN_DIR}/qcloseevent_wrapper.cpp ${QtGui_GEN_DIR}/qcolor_wrapper.cpp @@ -129,6 +130,7 @@ ${QtGui_GEN_DIR}/qexposeevent_wrapper.cpp ${QtGui_GEN_DIR}/qfileopenevent_wrapper.cpp ${QtGui_GEN_DIR}/qfocusevent_wrapper.cpp ${QtGui_GEN_DIR}/qfont_wrapper.cpp +${QtGui_GEN_DIR}/qfont_tag_wrapper.cpp ${QtGui_GEN_DIR}/qfontdatabase_wrapper.cpp ${QtGui_GEN_DIR}/qfontinfo_wrapper.cpp ${QtGui_GEN_DIR}/qfontmetrics_wrapper.cpp @@ -170,10 +172,8 @@ ${QtGui_GEN_DIR}/qmouseevent_wrapper.cpp ${QtGui_GEN_DIR}/qmoveevent_wrapper.cpp ${QtGui_GEN_DIR}/qmovie_wrapper.cpp ${QtGui_GEN_DIR}/qnativegestureevent_wrapper.cpp +${QtGui_GEN_DIR}/qnativeinterface_wrapper.cpp ${QtGui_GEN_DIR}/qoffscreensurface_wrapper.cpp -${QtGui_GEN_DIR}/qopenglcontextgroup_wrapper.cpp -${QtGui_GEN_DIR}/qopenglextrafunctions_wrapper.cpp -${QtGui_GEN_DIR}/qopenglfunctions_wrapper.cpp ${QtGui_GEN_DIR}/qpagedpaintdevice_wrapper.cpp ${QtGui_GEN_DIR}/qpagelayout_wrapper.cpp ${QtGui_GEN_DIR}/qpageranges_wrapper.cpp @@ -217,7 +217,6 @@ ${QtGui_GEN_DIR}/qsinglepointevent_wrapper.cpp ${QtGui_GEN_DIR}/qstandarditem_wrapper.cpp ${QtGui_GEN_DIR}/qstandarditemmodel_wrapper.cpp ${QtGui_GEN_DIR}/qstatustipevent_wrapper.cpp -${QtGui_GEN_DIR}/qopenglcontext_wrapper.cpp ${QtGui_GEN_DIR}/qaccessible_state_wrapper.cpp ${QtGui_GEN_DIR}/qaccessibleinterface_wrapper.cpp ${QtGui_GEN_DIR}/qscreen_wrapper.cpp @@ -283,6 +282,33 @@ ${QtGui_GEN_DIR}/qwindowstatechangeevent_wrapper.cpp ${QtGui_GEN_DIR}/qtgui_module_wrapper.cpp ) +get_property(QtGui_enabled_features TARGET Qt${QT_MAJOR_VERSION}::Gui + PROPERTY QT_ENABLED_PUBLIC_FEATURES) + +if("xcb" IN_LIST QtGui_enabled_features) + list(APPEND QtGui_SRC + ${QtGui_GEN_DIR}/qnativeinterface_qx11application_wrapper.cpp) +elseif(WIN32) + list(APPEND QtGui_SRC + ${QtGui_GEN_DIR}/qnativeinterface_qwindowsscreen_wrapper.cpp) +endif() + +if("opengl" IN_LIST QtGui_enabled_features OR "opengles2" IN_LIST QtGui_enabled_features + OR "opengles3" IN_LIST QtGui_enabled_features) + list(APPEND QtGui_SRC + ${QtGui_GEN_DIR}/qopenglcontextgroup_wrapper.cpp + ${QtGui_GEN_DIR}/qopenglextrafunctions_wrapper.cpp + ${QtGui_GEN_DIR}/qopenglfunctions_wrapper.cpp + ${QtGui_GEN_DIR}/qopenglcontext_wrapper.cpp + ${QtGui_GEN_DIR}/qrhigles2initparams_wrapper.cpp + ${QtGui_GEN_DIR}/qrhigles2nativehandles_wrapper.cpp) +else() + list(APPEND QtGui_DROPPED_ENTRIES + QOpenGLContext QOpenGLContextGroup QOpenGLPaintDevice + QOpenGLExtraFunctions QOpenGLFunctions + QRhiGles2InitParams QRhiGles2NativeHandles) +endif() + set(QtGui_private_include_dirs ${Qt${QT_MAJOR_VERSION}Core_PRIVATE_INCLUDE_DIRS} ${Qt${QT_MAJOR_VERSION}Gui_PRIVATE_INCLUDE_DIRS}) diff --git a/sources/pyside6/PySide6/QtGui/typesystem_gui.xml b/sources/pyside6/PySide6/QtGui/typesystem_gui.xml index 29c221f3..85898940 100644 --- a/sources/pyside6/PySide6/QtGui/typesystem_gui.xml +++ b/sources/pyside6/PySide6/QtGui/typesystem_gui.xml @@ -3,7 +3,8 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + diff --git a/sources/pyside6/PySide6/QtGui/typesystem_gui_common.xml b/sources/pyside6/PySide6/QtGui/typesystem_gui_common.xml index 7aaa6fe2..0cab80a1 100644 --- a/sources/pyside6/PySide6/QtGui/typesystem_gui_common.xml +++ b/sources/pyside6/PySide6/QtGui/typesystem_gui_common.xml @@ -122,21 +122,31 @@ + - - - + + + - - - - - - + + + + + + @@ -480,6 +490,7 @@ + @@ -600,9 +611,9 @@ - + - + @@ -615,6 +626,7 @@ + @@ -1094,14 +1106,14 @@ - + - + @@ -1302,37 +1314,38 @@ - - - + + + - + - + - + - + @@ -1344,22 +1357,29 @@ - - - - + + + + + - - - - + + + + - + @@ -1372,42 +1392,50 @@ - + - + - - + + - + - + - - - - + + + + - - - + + + @@ -1415,15 +1443,17 @@ - + - - + + @@ -1433,8 +1463,8 @@ - + @@ -1450,8 +1480,8 @@ - - + + @@ -1461,7 +1491,8 @@ - + @@ -1575,7 +1606,7 @@ - + @@ -2385,10 +2416,27 @@ - - - - + + + + + + + + + + + + + + + + + + + @@ -2585,6 +2633,13 @@ + + + + + + @@ -2594,6 +2649,31 @@ + + + + + + + + + + + + + + + + + + + + + @@ -3041,6 +3121,13 @@ + + + + + + diff --git a/sources/pyside6/PySide6/QtGui/typesystem_gui_rhi.xml b/sources/pyside6/PySide6/QtGui/typesystem_gui_rhi.xml index d3727675..22ab56e2 100644 --- a/sources/pyside6/PySide6/QtGui/typesystem_gui_rhi.xml +++ b/sources/pyside6/PySide6/QtGui/typesystem_gui_rhi.xml @@ -100,6 +100,7 @@ + + + + + + + + + diff --git a/sources/pyside6/PySide6/QtGui/typesystem_gui_x11.xml b/sources/pyside6/PySide6/QtGui/typesystem_gui_x11.xml index c4f16911..70331e29 100644 --- a/sources/pyside6/PySide6/QtGui/typesystem_gui_x11.xml +++ b/sources/pyside6/PySide6/QtGui/typesystem_gui_x11.xml @@ -3,4 +3,7 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + + + + diff --git a/sources/pyside6/PySide6/QtHelp/CMakeLists.txt b/sources/pyside6/PySide6/QtHelp/CMakeLists.txt index 88697dba..7bf46dce 100644 --- a/sources/pyside6/PySide6/QtHelp/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtHelp/CMakeLists.txt @@ -8,6 +8,7 @@ ${QtHelp_GEN_DIR}/qcompressedhelpinfo_wrapper.cpp ${QtHelp_GEN_DIR}/qhelpcontentitem_wrapper.cpp ${QtHelp_GEN_DIR}/qhelpcontentmodel_wrapper.cpp ${QtHelp_GEN_DIR}/qhelpcontentwidget_wrapper.cpp +${QtHelp_GEN_DIR}/qhelpglobal_wrapper.cpp ${QtHelp_GEN_DIR}/qhelpengine_wrapper.cpp ${QtHelp_GEN_DIR}/qhelpenginecore_wrapper.cpp ${QtHelp_GEN_DIR}/qhelpfilterdata_wrapper.cpp @@ -40,9 +41,6 @@ set(QtHelp_include_dirs ${QtHelp_SOURCE_DIR} ${QtCore_GEN_DIR} ) set(QtHelp_libraries pyside6 - ${Qt${QT_MAJOR_VERSION}Core_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Gui_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Widgets_LIBRARIES} ${Qt${QT_MAJOR_VERSION}Help_LIBRARIES}) set(QtHelp_deps QtWidgets) diff --git a/sources/pyside6/PySide6/QtHelp/typesystem_help.xml b/sources/pyside6/PySide6/QtHelp/typesystem_help.xml index 88712bfb..5fa30e03 100644 --- a/sources/pyside6/PySide6/QtHelp/typesystem_help.xml +++ b/sources/pyside6/PySide6/QtHelp/typesystem_help.xml @@ -4,7 +4,8 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + @@ -15,8 +16,9 @@ - + + diff --git a/sources/pyside6/PySide6/QtHttpServer/CMakeLists.txt b/sources/pyside6/PySide6/QtHttpServer/CMakeLists.txt index 9bd43e62..c931f064 100644 --- a/sources/pyside6/PySide6/QtHttpServer/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtHttpServer/CMakeLists.txt @@ -28,11 +28,7 @@ set(QtHttpServer_include_dirs ${QtHttpServer_SOURCE_DIR} ${QtWebSockets_GEN_DIR}) set(QtHttpServer_libraries pyside6 - ${Qt${QT_MAJOR_VERSION}HttpServer_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Core_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Concurrent_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Network_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}WebSockets_LIBRARIES}) + ${Qt${QT_MAJOR_VERSION}HttpServer_LIBRARIES}) set(QtHttpServer_deps QtCore QtConcurrent QtNetwork QtWebSockets) diff --git a/sources/pyside6/PySide6/QtHttpServer/typesystem_httpserver.xml b/sources/pyside6/PySide6/QtHttpServer/typesystem_httpserver.xml index 7504fafe..026a8758 100644 --- a/sources/pyside6/PySide6/QtHttpServer/typesystem_httpserver.xml +++ b/sources/pyside6/PySide6/QtHttpServer/typesystem_httpserver.xml @@ -3,7 +3,8 @@ // Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + diff --git a/sources/pyside6/PySide6/QtLocation/typesystem_location.xml b/sources/pyside6/PySide6/QtLocation/typesystem_location.xml index a44896cd..e35b7bd1 100644 --- a/sources/pyside6/PySide6/QtLocation/typesystem_location.xml +++ b/sources/pyside6/PySide6/QtLocation/typesystem_location.xml @@ -3,7 +3,8 @@ // Copyright (C) 2018 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + diff --git a/sources/pyside6/PySide6/QtMultimedia/CMakeLists.txt b/sources/pyside6/PySide6/QtMultimedia/CMakeLists.txt index 9e39680e..e40c5a2e 100644 --- a/sources/pyside6/PySide6/QtMultimedia/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtMultimedia/CMakeLists.txt @@ -29,9 +29,12 @@ ${QtMultimedia_GEN_DIR}/qmediatimerange_wrapper.cpp ${QtMultimedia_GEN_DIR}/qmediatimerange_interval_wrapper.cpp ${QtMultimedia_GEN_DIR}/qscreencapture_wrapper.cpp ${QtMultimedia_GEN_DIR}/qsoundeffect_wrapper.cpp +${QtMultimedia_GEN_DIR}/qtvideo_wrapper.cpp ${QtMultimedia_GEN_DIR}/qvideoframe_wrapper.cpp +${QtMultimedia_GEN_DIR}/qvideoframe_paintoptions_wrapper.cpp ${QtMultimedia_GEN_DIR}/qvideoframeformat_wrapper.cpp ${QtMultimedia_GEN_DIR}/qvideosink_wrapper.cpp +${QtMultimedia_GEN_DIR}/qwavedecoder_wrapper.cpp ${QtMultimedia_GEN_DIR}/qwindowcapture_wrapper.cpp # module is always needed @@ -50,11 +53,8 @@ set(QtMultimedia_include_dirs ${QtMultimedia_SOURCE_DIR} ${QtNetwork_GEN_DIR}) set(QtMultimedia_libraries pyside6 - ${Qt${QT_MAJOR_VERSION}Multimedia_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Core_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Gui_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Network_LIBRARIES} - ) + ${Qt${QT_MAJOR_VERSION}Multimedia_LIBRARIES}) + set(QtMultimedia_deps QtCore QtGui QtNetwork) create_pyside_module(NAME QtMultimedia diff --git a/sources/pyside6/PySide6/QtMultimedia/typesystem_multimedia.xml b/sources/pyside6/PySide6/QtMultimedia/typesystem_multimedia.xml index e9b601b1..2791f695 100644 --- a/sources/pyside6/PySide6/QtMultimedia/typesystem_multimedia.xml +++ b/sources/pyside6/PySide6/QtMultimedia/typesystem_multimedia.xml @@ -3,7 +3,8 @@ // Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + @@ -12,6 +13,13 @@ + + + + + @@ -46,29 +54,31 @@ - + - + + - + - + + @@ -144,6 +154,9 @@ + + + @@ -154,6 +167,11 @@ + + + + + diff --git a/sources/pyside6/PySide6/QtMultimediaWidgets/CMakeLists.txt b/sources/pyside6/PySide6/QtMultimediaWidgets/CMakeLists.txt index e3d8ca0e..c3988551 100644 --- a/sources/pyside6/PySide6/QtMultimediaWidgets/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtMultimediaWidgets/CMakeLists.txt @@ -26,12 +26,8 @@ set(QtMultimediaWidgets_include_dirs ${QtMultimediaWidgets_SOURCE_DIR} ${QtMultimedia_GEN_DIR}) set(QtMultimediaWidgets_libraries pyside6 - ${Qt${QT_MAJOR_VERSION}Multimedia_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}MultimediaWidgets_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Core_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Gui_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Network_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Widgets_LIBRARIES}) + ${Qt${QT_MAJOR_VERSION}MultimediaWidgets_LIBRARIES}) + set(QtMultimediaWidgets_deps QtCore QtGui QtNetwork QtWidgets QtMultimedia) diff --git a/sources/pyside6/PySide6/QtMultimediaWidgets/typesystem_multimediawidgets.xml b/sources/pyside6/PySide6/QtMultimediaWidgets/typesystem_multimediawidgets.xml index 8d53e743..05b4e245 100644 --- a/sources/pyside6/PySide6/QtMultimediaWidgets/typesystem_multimediawidgets.xml +++ b/sources/pyside6/PySide6/QtMultimediaWidgets/typesystem_multimediawidgets.xml @@ -3,7 +3,8 @@ // Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + diff --git a/sources/pyside6/PySide6/QtNetwork/CMakeLists.txt b/sources/pyside6/PySide6/QtNetwork/CMakeLists.txt index 21974f92..529e2e86 100644 --- a/sources/pyside6/PySide6/QtNetwork/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtNetwork/CMakeLists.txt @@ -18,6 +18,8 @@ ${QtNetwork_GEN_DIR}/qdnstextrecord_wrapper.cpp ${QtNetwork_GEN_DIR}/qhostaddress_wrapper.cpp ${QtNetwork_GEN_DIR}/qhostinfo_wrapper.cpp ${QtNetwork_GEN_DIR}/qhstspolicy_wrapper.cpp +${QtNetwork_GEN_DIR}/qhttp1configuration_wrapper.cpp +${QtNetwork_GEN_DIR}/qhttpheaders_wrapper.cpp ${QtNetwork_GEN_DIR}/qhttpmultipart_wrapper.cpp ${QtNetwork_GEN_DIR}/qhttppart_wrapper.cpp ${QtNetwork_GEN_DIR}/qhttp2configuration_wrapper.cpp @@ -38,7 +40,10 @@ ${QtNetwork_GEN_DIR}/qnetworkproxyfactory_wrapper.cpp ${QtNetwork_GEN_DIR}/qnetworkproxyquery_wrapper.cpp ${QtNetwork_GEN_DIR}/qnetworkreply_wrapper.cpp ${QtNetwork_GEN_DIR}/qnetworkrequest_wrapper.cpp +${QtNetwork_GEN_DIR}/qnetworkrequestfactory_wrapper.cpp ${QtNetwork_GEN_DIR}/qpassworddigestor_wrapper.cpp +${QtNetwork_GEN_DIR}/qrestaccessmanager_wrapper.cpp +${QtNetwork_GEN_DIR}/qrestreply_wrapper.cpp ${QtNetwork_GEN_DIR}/qssl_wrapper.cpp ${QtNetwork_GEN_DIR}/qsslcertificate_wrapper.cpp ${QtNetwork_GEN_DIR}/qsslcertificateextension_wrapper.cpp @@ -80,7 +85,8 @@ if("dtls" IN_LIST QtNetwork_disabled_features) else() list(APPEND QtNetwork_SRC ${QtNetwork_GEN_DIR}/qdtls_wrapper.cpp - ${QtNetwork_GEN_DIR}/qdtlsclientverifier_wrapper.cpp) + ${QtNetwork_GEN_DIR}/qdtlsclientverifier_wrapper.cpp + ${QtNetwork_GEN_DIR}/qdtlsclientverifier_generatorparameters_wrapper.cpp) message(STATUS "Qt${QT_MAJOR_VERSION}Network: Adding DTLS classes") endif() diff --git a/sources/pyside6/PySide6/QtNetwork/typesystem_network.xml b/sources/pyside6/PySide6/QtNetwork/typesystem_network.xml index fbbf7dfa..4dc7c9b0 100644 --- a/sources/pyside6/PySide6/QtNetwork/typesystem_network.xml +++ b/sources/pyside6/PySide6/QtNetwork/typesystem_network.xml @@ -3,7 +3,8 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + @@ -75,11 +76,18 @@ + + + + + + + @@ -171,6 +179,11 @@ + + + + + @@ -198,6 +211,8 @@ + - - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sources/pyside6/PySide6/QtNetworkAuth/CMakeLists.txt b/sources/pyside6/PySide6/QtNetworkAuth/CMakeLists.txt index d1d5d408..4d9498e4 100644 --- a/sources/pyside6/PySide6/QtNetworkAuth/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtNetworkAuth/CMakeLists.txt @@ -26,8 +26,6 @@ set(QtNetworkAuth_include_dirs ${QtNetworkAuth_SOURCE_DIR} ${QtNetworkAuth_GEN_DIR}) set(QtNetworkAuth_libraries pyside6 - ${Qt${QT_MAJOR_VERSION}Core_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Network_LIBRARIES} ${Qt${QT_MAJOR_VERSION}NetworkAuth_LIBRARIES}) set(QtNetworkAuth_deps QtNetwork) diff --git a/sources/pyside6/PySide6/QtNetworkAuth/typesystem_networkauth.xml b/sources/pyside6/PySide6/QtNetworkAuth/typesystem_networkauth.xml index a46dd2fc..bedfc5a2 100644 --- a/sources/pyside6/PySide6/QtNetworkAuth/typesystem_networkauth.xml +++ b/sources/pyside6/PySide6/QtNetworkAuth/typesystem_networkauth.xml @@ -4,13 +4,16 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + + diff --git a/sources/pyside6/PySide6/QtNfc/CMakeLists.txt b/sources/pyside6/PySide6/QtNfc/CMakeLists.txt index 7ecb6e73..b94249a5 100644 --- a/sources/pyside6/PySide6/QtNfc/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtNfc/CMakeLists.txt @@ -14,6 +14,7 @@ set(QtNfc_SRC ${QtNfc_GEN_DIR}/qndefnfcurirecord_wrapper.cpp ${QtNfc_GEN_DIR}/qnearfieldmanager_wrapper.cpp ${QtNfc_GEN_DIR}/qnearfieldtarget_wrapper.cpp + ${QtNfc_GEN_DIR}/qnearfieldtarget_requestid_wrapper.cpp # module is always needed ${QtNfc_GEN_DIR}/qtnfc_module_wrapper.cpp) diff --git a/sources/pyside6/PySide6/QtNfc/typesystem_nfc.xml b/sources/pyside6/PySide6/QtNfc/typesystem_nfc.xml index 91d6119c..b548227f 100644 --- a/sources/pyside6/PySide6/QtNfc/typesystem_nfc.xml +++ b/sources/pyside6/PySide6/QtNfc/typesystem_nfc.xml @@ -3,7 +3,8 @@ // Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + @@ -27,6 +28,7 @@ + diff --git a/sources/pyside6/PySide6/QtOpenGL/CMakeLists.txt b/sources/pyside6/PySide6/QtOpenGL/CMakeLists.txt index a5ee6c6f..5bf8b5f3 100644 --- a/sources/pyside6/PySide6/QtOpenGL/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtOpenGL/CMakeLists.txt @@ -87,8 +87,6 @@ set(QtOpenGL_include_dirs ${QtOpenGL_SOURCE_DIR} ${QtOpenGL_GEN_DIR} ) set(QtOpenGL_libraries pyside6 - ${Qt${QT_MAJOR_VERSION}Core_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Gui_LIBRARIES} ${Qt${QT_MAJOR_VERSION}OpenGL_LIBRARIES}) set(QtOpenGL_deps QtGui) diff --git a/sources/pyside6/PySide6/QtOpenGL/typesystem_opengl.xml b/sources/pyside6/PySide6/QtOpenGL/typesystem_opengl.xml index 53bec0fd..efbd1605 100644 --- a/sources/pyside6/PySide6/QtOpenGL/typesystem_opengl.xml +++ b/sources/pyside6/PySide6/QtOpenGL/typesystem_opengl.xml @@ -3,7 +3,8 @@ // Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + @@ -624,7 +625,7 @@ - + diff --git a/sources/pyside6/PySide6/QtOpenGLWidgets/CMakeLists.txt b/sources/pyside6/PySide6/QtOpenGLWidgets/CMakeLists.txt index 14f9afae..8c738613 100644 --- a/sources/pyside6/PySide6/QtOpenGLWidgets/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtOpenGLWidgets/CMakeLists.txt @@ -21,10 +21,6 @@ set(QtOpenGLWidgets_include_dirs ${QtOpenGLWidgets_SOURCE_DIR} ${QtOpenGLWidgets_GEN_DIR}) set(QtOpenGLWidgets_libraries pyside6 - ${Qt${QT_MAJOR_VERSION}Core_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Gui_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}OpenGL_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Widgets_LIBRARIES} ${Qt${QT_MAJOR_VERSION}OpenGLWidgets_LIBRARIES}) set(QtOpenGLWidgets_deps QtOpenGL QtWidgets) diff --git a/sources/pyside6/PySide6/QtOpenGLWidgets/typesystem_openglwidgets.xml b/sources/pyside6/PySide6/QtOpenGLWidgets/typesystem_openglwidgets.xml index 27dbd8b1..1e308a3f 100644 --- a/sources/pyside6/PySide6/QtOpenGLWidgets/typesystem_openglwidgets.xml +++ b/sources/pyside6/PySide6/QtOpenGLWidgets/typesystem_openglwidgets.xml @@ -3,7 +3,8 @@ // Copyright (C) 2020 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + diff --git a/sources/pyside6/PySide6/QtPdf/CMakeLists.txt b/sources/pyside6/PySide6/QtPdf/CMakeLists.txt index c058e9a5..5b9c1457 100644 --- a/sources/pyside6/PySide6/QtPdf/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtPdf/CMakeLists.txt @@ -29,10 +29,7 @@ set(QtPdf_include_dirs ${QtPdf_SOURCE_DIR} ${QtNetwork_GEN_DIR}) set(QtPdf_libraries pyside6 - ${Qt${QT_MAJOR_VERSION}Pdf_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Core_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Gui_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Network_LIBRARIES}) + ${Qt${QT_MAJOR_VERSION}Pdf_LIBRARIES}) set(QtPdf_deps QtCore QtGui QtNetwork) diff --git a/sources/pyside6/PySide6/QtPdf/typesystem_pdf.xml b/sources/pyside6/PySide6/QtPdf/typesystem_pdf.xml index 229421ad..e008e488 100644 --- a/sources/pyside6/PySide6/QtPdf/typesystem_pdf.xml +++ b/sources/pyside6/PySide6/QtPdf/typesystem_pdf.xml @@ -3,7 +3,8 @@ // Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + diff --git a/sources/pyside6/PySide6/QtPdfWidgets/CMakeLists.txt b/sources/pyside6/PySide6/QtPdfWidgets/CMakeLists.txt index 93ad3638..de1335f8 100644 --- a/sources/pyside6/PySide6/QtPdfWidgets/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtPdfWidgets/CMakeLists.txt @@ -26,12 +26,7 @@ set(QtPdfWidgets_include_dirs ${QtPdfWidgets_SOURCE_DIR} ${QtPdf_GEN_DIR}) set(QtPdfWidgets_libraries pyside6 - ${Qt${QT_MAJOR_VERSION}Pdf_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}PdfWidgets_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Core_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Gui_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Network_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Widgets_LIBRARIES}) + ${Qt${QT_MAJOR_VERSION}PdfWidgets_LIBRARIES}) set(QtPdfWidgets_deps QtCore QtGui QtNetwork QtWidgets QtPdf) diff --git a/sources/pyside6/PySide6/QtPdfWidgets/typesystem_pdfwidgets.xml b/sources/pyside6/PySide6/QtPdfWidgets/typesystem_pdfwidgets.xml index 5f15e5ec..5df72075 100644 --- a/sources/pyside6/PySide6/QtPdfWidgets/typesystem_pdfwidgets.xml +++ b/sources/pyside6/PySide6/QtPdfWidgets/typesystem_pdfwidgets.xml @@ -3,7 +3,8 @@ // Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + diff --git a/sources/pyside6/PySide6/QtPositioning/CMakeLists.txt b/sources/pyside6/PySide6/QtPositioning/CMakeLists.txt index d8622e68..cad6dcb7 100644 --- a/sources/pyside6/PySide6/QtPositioning/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtPositioning/CMakeLists.txt @@ -23,6 +23,7 @@ ${QtPositioning_GEN_DIR}/qgeosatelliteinfo_wrapper.cpp ${QtPositioning_GEN_DIR}/qgeosatelliteinfosource_wrapper.cpp ${QtPositioning_GEN_DIR}/qgeoshape_wrapper.cpp ${QtPositioning_GEN_DIR}/qnmeapositioninfosource_wrapper.cpp +${QtPositioning_GEN_DIR}/qnmeasatelliteinfosource_wrapper.cpp # module is always needed ${QtPositioning_GEN_DIR}/qtpositioning_module_wrapper.cpp ) @@ -46,3 +47,14 @@ create_pyside_module(NAME QtPositioning TYPESYSTEM_PATH QtPositioning_SOURCE_DIR SOURCES QtPositioning_SRC DROPPED_ENTRIES QtPositioning_DROPPED_ENTRIES) + +if (APPLE) + # The QtLocation permission plugin cannot be linked to QtCore.abi3.so because for a framework + # build of Qt, the QtCore framework bundle must be loaded before calling + # Q_IMPORT_PLUGIN(QDarwinLocationPermissionPlugin) + set(permission_plugin_name "QDarwinLocationPermissionPlugin") + set(permission_plugin "${QT_CMAKE_EXPORT_NAMESPACE}::${permission_plugin_name}") + set_target_properties(QtPositioning PROPERTIES "_qt_has_${permission_plugin_name}_usage_description" TRUE) + # importing the plugin + qt6_import_plugins(QtPositioning INCLUDE ${permission_plugin}) +endif() diff --git a/sources/pyside6/PySide6/QtPositioning/typesystem_positioning.xml b/sources/pyside6/PySide6/QtPositioning/typesystem_positioning.xml index 359a7062..8dac3f00 100644 --- a/sources/pyside6/PySide6/QtPositioning/typesystem_positioning.xml +++ b/sources/pyside6/PySide6/QtPositioning/typesystem_positioning.xml @@ -3,8 +3,11 @@ // Copyright (C) 2018 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + + @@ -41,4 +44,8 @@ + + + + diff --git a/sources/pyside6/PySide6/QtPrintSupport/CMakeLists.txt b/sources/pyside6/PySide6/QtPrintSupport/CMakeLists.txt index 615d202f..fb0db5f8 100644 --- a/sources/pyside6/PySide6/QtPrintSupport/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtPrintSupport/CMakeLists.txt @@ -25,14 +25,11 @@ set(QtPrintSupport_include_dirs ${QtPrintSupport_SOURCE_DIR} ${libpyside_SOURCE_DIR} ${QtCore_GEN_DIR} ${QtGui_GEN_DIR} - ${QtWidgets_GEN_DIR} - ) + ${QtWidgets_GEN_DIR}) + set(QtPrintSupport_libraries pyside6 - ${Qt${QT_MAJOR_VERSION}Core_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Gui_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Widgets_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}PrintSupport_LIBRARIES} - ) + ${Qt${QT_MAJOR_VERSION}PrintSupport_LIBRARIES}) + set(QtPrintSupport_deps QtWidgets) create_pyside_module(NAME QtPrintSupport INCLUDE_DIRS QtPrintSupport_include_dirs diff --git a/sources/pyside6/PySide6/QtPrintSupport/typesystem_printsupport.xml b/sources/pyside6/PySide6/QtPrintSupport/typesystem_printsupport.xml index 58d4a54b..46973a5f 100644 --- a/sources/pyside6/PySide6/QtPrintSupport/typesystem_printsupport.xml +++ b/sources/pyside6/PySide6/QtPrintSupport/typesystem_printsupport.xml @@ -3,7 +3,8 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + diff --git a/sources/pyside6/PySide6/QtQml/CMakeLists.txt b/sources/pyside6/PySide6/QtQml/CMakeLists.txt index 676f55a2..87de1d7d 100644 --- a/sources/pyside6/PySide6/QtQml/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtQml/CMakeLists.txt @@ -63,8 +63,6 @@ set(QtQml_include_dirs ${QtQml_SOURCE_DIR} ${QtQml_GEN_DIR}) set(QtQml_libraries pyside6 pyside6qml - ${Qt${QT_MAJOR_VERSION}Core_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Network_LIBRARIES} ${Qt${QT_MAJOR_VERSION}Qml_LIBRARIES}) set(QtQml_deps QtNetwork) diff --git a/sources/pyside6/PySide6/QtQml/pysideqmlvolatilebool.cpp b/sources/pyside6/PySide6/QtQml/pysideqmlvolatilebool.cpp index e92e13aa..ca3dfebe 100644 --- a/sources/pyside6/PySide6/QtQml/pysideqmlvolatilebool.cpp +++ b/sources/pyside6/PySide6/QtQml/pysideqmlvolatilebool.cpp @@ -3,6 +3,7 @@ #include "pysideqmlvolatilebool.h" +#include #include #include @@ -30,8 +31,7 @@ QtQml_VolatileBoolObject_new(PyTypeObject *type, PyObject *args, PyObject *kwds) if (ok < 0) return nullptr; - QtQml_VolatileBoolObject *self - = reinterpret_cast(type->tp_alloc(type, 0)); + auto *self = PepExt_TypeCallAlloc(type, 0); if (self != nullptr) self->flag = new AtomicBool(ok); @@ -49,7 +49,10 @@ static void QtQml_VolatileBoolObject_dealloc(PyObject *self) static PyObject * QtQml_VolatileBoolObject_get(QtQml_VolatileBoolObject *self) { - return *self->flag ? Py_True : Py_False; + if (*self->flag) { + Py_RETURN_TRUE; + } + Py_RETURN_FALSE; } static PyObject * @@ -63,10 +66,8 @@ QtQml_VolatileBoolObject_set(QtQml_VolatileBoolObject *self, PyObject *args) } ok = PyObject_IsTrue(value); - if (ok < 0) { - PyErr_SetString(PyExc_TypeError, "Not a boolean value."); - return nullptr; - } + if (ok < 0) + return PyErr_Format(PyExc_TypeError, "Not a boolean value."); *self->flag = ok > 0; diff --git a/sources/pyside6/PySide6/QtQml/typesystem_qml.xml b/sources/pyside6/PySide6/QtQml/typesystem_qml.xml index b5538dcb..3392ef37 100644 --- a/sources/pyside6/PySide6/QtQml/typesystem_qml.xml +++ b/sources/pyside6/PySide6/QtQml/typesystem_qml.xml @@ -3,7 +3,8 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + @@ -37,7 +38,7 @@ in generator tests folder. --> - + @@ -52,31 +53,43 @@ + + + + + + @@ -97,7 +110,7 @@ - + @@ -108,12 +121,15 @@ - - - - - - + + + + + + @@ -199,6 +215,30 @@ + + + + + + + + + + + + + + + + diff --git a/sources/pyside6/PySide6/QtQuick/CMakeLists.txt b/sources/pyside6/PySide6/QtQuick/CMakeLists.txt index 497d3181..77523024 100644 --- a/sources/pyside6/PySide6/QtQuick/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtQuick/CMakeLists.txt @@ -3,6 +3,8 @@ project(QtQuick) +set(QtQuick_DROPPED_ENTRIES) + set(QtQuick_registerType "${QtQuick_SOURCE_DIR}/pysidequickregistertype.cpp") # Exclude sources that have clashing static helper functions named "renderstate_..." @@ -17,8 +19,6 @@ set_property(SOURCE ${QtQuick_SRC_UNITY_EXCLUDED_SRC} set(QtQuick_SRC ${QtQuick_SRC_UNITY_EXCLUDED_SRC} ${QtQuick_GEN_DIR}/qquickasyncimageprovider_wrapper.cpp -${QtQuick_GEN_DIR}/qquickframebufferobject_wrapper.cpp -${QtQuick_GEN_DIR}/qquickframebufferobject_renderer_wrapper.cpp ${QtQuick_GEN_DIR}/qquickgraphicsconfiguration_wrapper.cpp ${QtQuick_GEN_DIR}/qquickgraphicsdevice_wrapper.cpp ${QtQuick_GEN_DIR}/qquicktexturefactory_wrapper.cpp @@ -32,9 +32,12 @@ ${QtQuick_GEN_DIR}/qsharedpointer_qquickitemgrabresult_wrapper.cpp ${QtQuick_GEN_DIR}/qquickpainteditem_wrapper.cpp ${QtQuick_GEN_DIR}/qquickrendercontrol_wrapper.cpp ${QtQuick_GEN_DIR}/qquickrendertarget_wrapper.cpp +${QtQuick_GEN_DIR}/qquickrhiitemrenderer_wrapper.cpp +${QtQuick_GEN_DIR}/qquickrhiitem_wrapper.cpp ${QtQuick_GEN_DIR}/qquicktextdocument_wrapper.cpp ${QtQuick_GEN_DIR}/qquickview_wrapper.cpp ${QtQuick_GEN_DIR}/qquickwindow_wrapper.cpp +${QtQuick_GEN_DIR}/qquickwindow_graphicsstateinfo_wrapper.cpp ${QtQuick_GEN_DIR}/qsgbasicgeometrynode_wrapper.cpp ${QtQuick_GEN_DIR}/qsgclipnode_wrapper.cpp ${QtQuick_GEN_DIR}/qsgdynamictexture_wrapper.cpp @@ -46,19 +49,25 @@ ${QtQuick_GEN_DIR}/qsggeometry_point2d_wrapper.cpp ${QtQuick_GEN_DIR}/qsggeometry_texturedpoint2d_wrapper.cpp ${QtQuick_GEN_DIR}/qsggeometry_wrapper.cpp ${QtQuick_GEN_DIR}/qsggeometrynode_wrapper.cpp +${QtQuick_GEN_DIR}/qsgimagenode_wrapper.cpp ${QtQuick_GEN_DIR}/qsgmaterial_wrapper.cpp ${QtQuick_GEN_DIR}/qsgmaterialshader_wrapper.cpp +${QtQuick_GEN_DIR}/qsgmaterialshader_graphicspipelinestate_wrapper.cpp ${QtQuick_GEN_DIR}/qsgmaterialtype_wrapper.cpp +${QtQuick_GEN_DIR}/qsgninepatchnode_wrapper.cpp ${QtQuick_GEN_DIR}/qsgnode_wrapper.cpp +${QtQuick_GEN_DIR}/qsgnodevisitor_wrapper.cpp ${QtQuick_GEN_DIR}/qsgopacitynode_wrapper.cpp ${QtQuick_GEN_DIR}/qsgopaquetexturematerial_wrapper.cpp #${QtQuick_GEN_DIR}/qsgsimplematerial_wrapper.cpp #${QtQuick_GEN_DIR}/qsgsimplematerialshader_wrapper.cpp ${QtQuick_GEN_DIR}/qsgrectanglenode_wrapper.cpp ${QtQuick_GEN_DIR}/qsgrendernode_wrapper.cpp +${QtQuick_GEN_DIR}/qsgrootnode_wrapper.cpp ${QtQuick_GEN_DIR}/qsgsimplerectnode_wrapper.cpp ${QtQuick_GEN_DIR}/qsgsimpletexturenode_wrapper.cpp ${QtQuick_GEN_DIR}/qsgrendererinterface_wrapper.cpp +${QtQuick_GEN_DIR}/qsgtextnode_wrapper.cpp ${QtQuick_GEN_DIR}/qsgtexture_wrapper.cpp ${QtQuick_GEN_DIR}/qsgtexturematerial_wrapper.cpp ${QtQuick_GEN_DIR}/qsgtextureprovider_wrapper.cpp @@ -73,28 +82,32 @@ set(QtQuick_include_dirs ${QtQuick_SOURCE_DIR} ${Qt${QT_MAJOR_VERSION}Core_INCLUDE_DIRS} ${Qt${QT_MAJOR_VERSION}Core_PRIVATE_INCLUDE_DIRS} ${Qt${QT_MAJOR_VERSION}Gui_INCLUDE_DIRS} - ${Qt${QT_MAJOR_VERSION}OpenGL_INCLUDE_DIRS} ${Qt${QT_MAJOR_VERSION}Network_INCLUDE_DIRS} ${Qt${QT_MAJOR_VERSION}Qml_INCLUDE_DIRS} ${Qt${QT_MAJOR_VERSION}Qml_PRIVATE_INCLUDE_DIRS} ${Qt${QT_MAJOR_VERSION}Quick_INCLUDE_DIRS} ${libpyside_SOURCE_DIR} ${QtGui_GEN_DIR} - ${QtOpenGL_GEN_DIR} ${QtCore_GEN_DIR} ${QtNetwork_GEN_DIR} ${QtQml_GEN_DIR} ${QtQuick_GEN_DIR}) set(QtQuick_libraries pyside6 pyside6qml - ${Qt${QT_MAJOR_VERSION}Core_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Gui_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}OpenGL_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Network_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Qml_LIBRARIES} ${Qt${QT_MAJOR_VERSION}Quick_LIBRARIES}) -set(QtQuick_deps QtGui QtOpenGL QtNetwork QtQml) +set(QtQuick_deps QtGui QtNetwork QtQml) + +check_qt_opengl("Quick" QtQuick_include_dirs QtQuick_deps + QtQuick_DROPPED_ENTRIES) + +if (Qt${QT_MAJOR_VERSION}OpenGL_FOUND) + list(APPEND QtQuick_SRC + ${QtQuick_GEN_DIR}/qquickframebufferobject_wrapper.cpp + ${QtQuick_GEN_DIR}/qquickframebufferobject_renderer_wrapper.cpp) +else() + list(APPEND QtQuick_DROPPED_ENTRIES QQuickFramebufferObject) +endif() create_pyside_module(NAME QtQuick INCLUDE_DIRS QtQuick_include_dirs @@ -102,4 +115,5 @@ create_pyside_module(NAME QtQuick DEPS QtQuick_deps TYPESYSTEM_PATH QtQuick_SOURCE_DIR SOURCES QtQuick_SRC - STATIC_SOURCES QtQuick_registerType) + STATIC_SOURCES QtQuick_registerType + DROPPED_ENTRIES QtQuick_DROPPED_ENTRIES) diff --git a/sources/pyside6/PySide6/QtQuick/pysidequickregistertype.cpp b/sources/pyside6/PySide6/QtQuick/pysidequickregistertype.cpp index f15e1f3d..f7749b4e 100644 --- a/sources/pyside6/PySide6/QtQuick/pysidequickregistertype.cpp +++ b/sources/pyside6/PySide6/QtQuick/pysidequickregistertype.cpp @@ -9,7 +9,10 @@ #include #include -#include + +#if QT_CONFIG(opengl) || QT_CONFIG(opengles2) || QT_CONFIG(opengles3) +# include +#endif bool pyTypeObjectInheritsFromClass(PyTypeObject *pyObjType, const char *classPtrName) { @@ -22,7 +25,7 @@ bool pyTypeObjectInheritsFromClass(PyTypeObject *pyObjType, const char *classPtr template bool registerTypeIfInheritsFromClass(const char *classPtrName, PyTypeObject *typeToRegister, - QQmlPrivate::RegisterType *type) + QQmlPrivate::RegisterTypeAndRevisions *type) { if (!pyTypeObjectInheritsFromClass(typeToRegister, classPtrName)) return false; @@ -35,7 +38,7 @@ bool registerTypeIfInheritsFromClass(const char *classPtrName, return true; } -bool quickRegisterType(PyObject *pyObj, QQmlPrivate::RegisterType *type) +bool quickRegisterType(PyObject *pyObj, QQmlPrivate::RegisterTypeAndRevisions *type) { using namespace Shiboken; @@ -51,8 +54,10 @@ bool quickRegisterType(PyObject *pyObj, QQmlPrivate::RegisterType *type) return registerTypeIfInheritsFromClass("QQuickPaintedItem*", pyObjType, type) +#if QT_CONFIG(opengl) || QT_CONFIG(opengles2) || QT_CONFIG(opengles3) || registerTypeIfInheritsFromClass("QQuickFramebufferObject*", pyObjType, type) +#endif || registerTypeIfInheritsFromClass("QQuickItem*", pyObjType, type); } @@ -62,7 +67,9 @@ void PySide::initQuickSupport(PyObject *module) Q_UNUSED(module); // We need to manually register a pointer version of these types in order for them to be used as property types. qRegisterMetaType("QQuickPaintedItem*"); +#if QT_CONFIG(opengl) || QT_CONFIG(opengles2) || QT_CONFIG(opengles3) qRegisterMetaType("QQuickFramebufferObject*"); +#endif qRegisterMetaType("QQuickItem*"); Qml::setQuickRegisterItemFunction(quickRegisterType); diff --git a/sources/pyside6/PySide6/QtQuick/typesystem_quick.xml b/sources/pyside6/PySide6/QtQuick/typesystem_quick.xml index a7f5ec49..0a24123f 100644 --- a/sources/pyside6/PySide6/QtQuick/typesystem_quick.xml +++ b/sources/pyside6/PySide6/QtQuick/typesystem_quick.xml @@ -3,11 +3,15 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + + + + + + + + + @@ -89,7 +98,9 @@ - + + + @@ -101,6 +112,7 @@ + @@ -156,6 +168,10 @@ + + + + @@ -165,22 +181,35 @@ + + + + + + + + + + + + + @@ -195,6 +224,7 @@ + diff --git a/sources/pyside6/PySide6/QtQuick3D/CMakeLists.txt b/sources/pyside6/PySide6/QtQuick3D/CMakeLists.txt index b98a1dd1..37f8ebfb 100644 --- a/sources/pyside6/PySide6/QtQuick3D/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtQuick3D/CMakeLists.txt @@ -3,6 +3,8 @@ project(QtQuick3D) +set (QtQuick3D_DROPPED_ENTRIES) + set(QtQuick3D_SRC ${QtQuick3D_GEN_DIR}/qquick3d_wrapper.cpp ${QtQuick3D_GEN_DIR}/qquick3dobject_wrapper.cpp @@ -11,6 +13,7 @@ ${QtQuick3D_GEN_DIR}/qquick3dgeometry_attribute_wrapper.cpp ${QtQuick3D_GEN_DIR}/qquick3dgeometry_targetattribute_wrapper.cpp ${QtQuick3D_GEN_DIR}/qquick3dinstancing_wrapper.cpp ${QtQuick3D_GEN_DIR}/qquick3dinstancing_instancetableentry_wrapper.cpp +${QtQuick3D_GEN_DIR}/qquick3drenderextension_wrapper.cpp ${QtQuick3D_GEN_DIR}/qquick3dtexturedata_wrapper.cpp # module is always needed ${QtQuick3D_GEN_DIR}/qtquick3d_module_wrapper.cpp @@ -21,7 +24,6 @@ set(QtQuick3D_include_dirs ${QtQuick3D_SOURCE_DIR} ${Qt${QT_MAJOR_VERSION}Core_INCLUDE_DIRS} ${Qt${QT_MAJOR_VERSION}Core_PRIVATE_INCLUDE_DIRS} ${Qt${QT_MAJOR_VERSION}Gui_INCLUDE_DIRS} - ${Qt${QT_MAJOR_VERSION}OpenGL_INCLUDE_DIRS} ${Qt${QT_MAJOR_VERSION}Network_INCLUDE_DIRS} ${Qt${QT_MAJOR_VERSION}Qml_INCLUDE_DIRS} ${Qt${QT_MAJOR_VERSION}Qml_PRIVATE_INCLUDE_DIRS} @@ -29,7 +31,6 @@ set(QtQuick3D_include_dirs ${QtQuick3D_SOURCE_DIR} ${Qt${QT_MAJOR_VERSION}Quick3D_INCLUDE_DIRS} ${libpyside_SOURCE_DIR} ${QtGui_GEN_DIR} - ${QtOpenGL_GEN_DIR} ${QtCore_GEN_DIR} ${QtNetwork_GEN_DIR} ${QtQml_GEN_DIR} @@ -37,19 +38,17 @@ set(QtQuick3D_include_dirs ${QtQuick3D_SOURCE_DIR} ${QtQuick3D_GEN_DIR}) set(QtQuick3D_libraries pyside6 - ${Qt${QT_MAJOR_VERSION}Core_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Gui_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}OpenGL_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Network_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Qml_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Quick_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Quick3D_LIBRARIES}) + ${Qt${QT_MAJOR_VERSION}Quick3D_LIBRARIES}) + +set(QtQuick3D_deps QtGui QtNetwork QtQml QtQuick) -set(QtQuick3D_deps QtGui QtOpenGL QtNetwork QtQml QtQuick) +check_qt_opengl("Quick3D" QtQuick3D_include_dirs QtQuick3D_deps + QtQuick3D_DROPPED_ENTRIES) create_pyside_module(NAME QtQuick3D INCLUDE_DIRS QtQuick3D_include_dirs LIBRARIES QtQuick3D_libraries DEPS QtQuick3D_deps TYPESYSTEM_PATH QtQuick3D_SOURCE_DIR - SOURCES QtQuick3D_SRC) + SOURCES QtQuick3D_SRC + DROPPED_ENTRIES QtQuick3D_DROPPED_ENTRIES) diff --git a/sources/pyside6/PySide6/QtQuick3D/typesystem_quick3d.xml b/sources/pyside6/PySide6/QtQuick3D/typesystem_quick3d.xml index 274272bc..fe8f4e70 100644 --- a/sources/pyside6/PySide6/QtQuick3D/typesystem_quick3d.xml +++ b/sources/pyside6/PySide6/QtQuick3D/typesystem_quick3d.xml @@ -3,7 +3,8 @@ // Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + @@ -25,4 +26,5 @@ + diff --git a/sources/pyside6/PySide6/QtQuickControls2/CMakeLists.txt b/sources/pyside6/PySide6/QtQuickControls2/CMakeLists.txt index 0ba33b5f..9951d2e1 100644 --- a/sources/pyside6/PySide6/QtQuickControls2/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtQuickControls2/CMakeLists.txt @@ -3,8 +3,11 @@ project(QtQuickControls2) +set (QtQuickControls2_DROPPED_ENTRIES) + set(QtQuickControls2_SRC ${QtQuickControls2_GEN_DIR}/qquickstyle_wrapper.cpp +${QtQuickControls2_GEN_DIR}/qquickattachedpropertypropagator_wrapper.cpp # module is always needed ${QtQuickControls2_GEN_DIR}/qtquickcontrols2_module_wrapper.cpp ) @@ -14,14 +17,12 @@ set(QtQuickControls2_include_dirs ${QtQuickControls2_SOURCE_DIR} ${QtQml_SOURCE_DIR} ${Qt${QT_MAJOR_VERSION}Core_INCLUDE_DIRS} ${Qt${QT_MAJOR_VERSION}Gui_INCLUDE_DIRS} - ${Qt${QT_MAJOR_VERSION}OpenGL_INCLUDE_DIRS} ${Qt${QT_MAJOR_VERSION}Network_INCLUDE_DIRS} ${Qt${QT_MAJOR_VERSION}Qml_INCLUDE_DIRS} ${Qt${QT_MAJOR_VERSION}Quick_INCLUDE_DIRS} ${Qt${QT_MAJOR_VERSION}QuickControls2_INCLUDE_DIRS} ${libpyside_SOURCE_DIR} ${QtGui_GEN_DIR} - ${QtOpenGL_GEN_DIR} ${QtCore_GEN_DIR} ${QtNetwork_GEN_DIR} ${QtQml_GEN_DIR} @@ -29,19 +30,17 @@ set(QtQuickControls2_include_dirs ${QtQuickControls2_SOURCE_DIR} ${QtQuickControls2_GEN_DIR}) set(QtQuickControls2_libraries pyside6 - ${Qt${QT_MAJOR_VERSION}Core_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Gui_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}OpenGL_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Network_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Qml_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Quick_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}QuickControls2_LIBRARIES}) + ${Qt${QT_MAJOR_VERSION}QuickControls2_LIBRARIES}) + +set(QtQuickControls2_deps QtGui QtNetwork QtQml QtQuick) -set(QtQuickControls2_deps QtGui QtOpenGL QtNetwork QtQml QtQuick) +check_qt_opengl("QuickControls2" QtQuickControls2_include_dirs QtQuickControls2_deps + QtQuickControls2_DROPPED_ENTRIES) create_pyside_module(NAME QtQuickControls2 INCLUDE_DIRS QtQuickControls2_include_dirs LIBRARIES QtQuickControls2_libraries DEPS QtQuickControls2_deps TYPESYSTEM_PATH QtQuickControls2_SOURCE_DIR - SOURCES QtQuickControls2_SRC) + SOURCES QtQuickControls2_SRC + DROPPED_ENTRIES QtQuickControls2_DROPPED_ENTRIES) diff --git a/sources/pyside6/PySide6/QtQuickControls2/typesystem_quickcontrols2.xml b/sources/pyside6/PySide6/QtQuickControls2/typesystem_quickcontrols2.xml index fa886b06..72fc0522 100644 --- a/sources/pyside6/PySide6/QtQuickControls2/typesystem_quickcontrols2.xml +++ b/sources/pyside6/PySide6/QtQuickControls2/typesystem_quickcontrols2.xml @@ -3,9 +3,11 @@ // Copyright (C) 2020 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + + diff --git a/sources/pyside6/PySide6/QtQuickTest/CMakeLists.txt b/sources/pyside6/PySide6/QtQuickTest/CMakeLists.txt new file mode 100644 index 00000000..8e3fb4d5 --- /dev/null +++ b/sources/pyside6/PySide6/QtQuickTest/CMakeLists.txt @@ -0,0 +1,44 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +project(QtQuickTest) + +set (QtQuickTest_DROPPED_ENTRIES) + +set(QtQuickTest_SRC +# module is always needed +${QtQuickTest_GEN_DIR}/qtquicktest_module_wrapper.cpp +) + +set(QtQuickTest_include_dirs ${QtQuickTest_SOURCE_DIR} + ${QtQml_SOURCE_DIR} + ${Qt${QT_MAJOR_VERSION}Core_INCLUDE_DIRS} + ${Qt${QT_MAJOR_VERSION}Gui_INCLUDE_DIRS} + ${Qt${QT_MAJOR_VERSION}OpenGL_INCLUDE_DIRS} + ${Qt${QT_MAJOR_VERSION}Network_INCLUDE_DIRS} + ${Qt${QT_MAJOR_VERSION}Qml_INCLUDE_DIRS} + ${Qt${QT_MAJOR_VERSION}Quick_INCLUDE_DIRS} + ${libpyside_SOURCE_DIR} + ${QtGui_GEN_DIR} + ${QtOpenGL_GEN_DIR} + ${QtCore_GEN_DIR} + ${QtNetwork_GEN_DIR} + ${QtQml_GEN_DIR} + ${QtQuick_GEN_DIR} + ${QtQuickTest_GEN_DIR}) + +set(QtQuickTest_libraries pyside6 + ${Qt${QT_MAJOR_VERSION}QuickTest_LIBRARIES}) + +set(QtQuickTest_deps QtGui QtNetwork QtQml QtQuick) + +check_qt_opengl("QuickTest" QtQuickTest_include_dirs QtQuickTest_deps + QtQuickTest_DROPPED_ENTRIES) + +create_pyside_module(NAME QtQuickTest + INCLUDE_DIRS QtQuickTest_include_dirs + LIBRARIES QtQuickTest_libraries + DEPS QtQuickTest_deps + TYPESYSTEM_PATH QtQuickTest_SOURCE_DIR + SOURCES QtQuickTest_SRC + DROPPED_ENTRIES QtQuickTest_DROPPED_ENTRIES) diff --git a/sources/pyside6/PySide6/QtQuickTest/typesystem_quicktest.xml b/sources/pyside6/PySide6/QtQuickTest/typesystem_quicktest.xml new file mode 100644 index 00000000..b6ef754e --- /dev/null +++ b/sources/pyside6/PySide6/QtQuickTest/typesystem_quicktest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/sources/pyside6/PySide6/QtQuickWidgets/CMakeLists.txt b/sources/pyside6/PySide6/QtQuickWidgets/CMakeLists.txt index bab2fa5b..883010be 100644 --- a/sources/pyside6/PySide6/QtQuickWidgets/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtQuickWidgets/CMakeLists.txt @@ -3,6 +3,8 @@ project(QtQuickWidgets) +set (QtQuickWidgets_DROPPED_ENTRIES) + set(QtQuickWidgets_SRC ${QtQuickWidgets_GEN_DIR}/qquickwidget_wrapper.cpp # module is always needed @@ -30,20 +32,17 @@ set(QtQuickWidgets_include_dirs ${QtQuickWidgets_SOURCE_DIR} ${QtQuickWidgets_GEN_DIR}) set(QtQuickWidgets_libraries pyside6 - ${Qt${QT_MAJOR_VERSION}Core_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Gui_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}OpenGL_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Network_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Widgets_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Quick_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Qml_LIBRARIES} ${Qt${QT_MAJOR_VERSION}QuickWidgets_LIBRARIES}) -set(QtQuickWidgets_deps QtGui QtOpenGL QtQml QtQuick QtWidgets QtNetwork) +set(QtQuickWidgets_deps QtGui QtQml QtQuick QtWidgets QtNetwork) + +check_qt_opengl("QuickWidgets" QtQuickWidgets_include_dirs QtQuickWidgets_deps + QtQuickWidgets_DROPPED_ENTRIES) create_pyside_module(NAME QtQuickWidgets INCLUDE_DIRS QtQuickWidgets_include_dirs LIBRARIES QtQuickWidgets_libraries DEPS QtQuickWidgets_deps TYPESYSTEM_PATH QtQuickWidgets_SOURCE_DIR - SOURCES QtQuickWidgets_SRC) + SOURCES QtQuickWidgets_SRC + DROPPED_ENTRIES QtQuickWidgets_DROPPED_ENTRIES) diff --git a/sources/pyside6/PySide6/QtQuickWidgets/typesystem_quickwidgets.xml b/sources/pyside6/PySide6/QtQuickWidgets/typesystem_quickwidgets.xml index 2768e73d..4f4484cb 100644 --- a/sources/pyside6/PySide6/QtQuickWidgets/typesystem_quickwidgets.xml +++ b/sources/pyside6/PySide6/QtQuickWidgets/typesystem_quickwidgets.xml @@ -3,7 +3,8 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + diff --git a/sources/pyside6/PySide6/QtRemoteObjects/CMakeLists.txt b/sources/pyside6/PySide6/QtRemoteObjects/CMakeLists.txt index f61bddf8..07835b2f 100644 --- a/sources/pyside6/PySide6/QtRemoteObjects/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtRemoteObjects/CMakeLists.txt @@ -5,6 +5,7 @@ project(QtRemoteObjects) set(QtRemoteObjects_SRC ${QtRemoteObjects_GEN_DIR}/qabstractitemmodelreplica_wrapper.cpp +${QtRemoteObjects_GEN_DIR}/qconnectionabstractserver_wrapper.cpp ${QtRemoteObjects_GEN_DIR}/qremoteobjectabstractpersistedstore_wrapper.cpp ${QtRemoteObjects_GEN_DIR}/qremoteobjectdynamicreplica_wrapper.cpp ${QtRemoteObjects_GEN_DIR}/qremoteobjecthost_wrapper.cpp @@ -15,9 +16,14 @@ ${QtRemoteObjects_GEN_DIR}/qremoteobjectpendingcallwatcher_wrapper.cpp ${QtRemoteObjects_GEN_DIR}/qremoteobjectregistry_wrapper.cpp ${QtRemoteObjects_GEN_DIR}/qremoteobjectregistryhost_wrapper.cpp ${QtRemoteObjects_GEN_DIR}/qremoteobjectreplica_wrapper.cpp -# ${QtRemoteObjects_GEN_DIR}/qtremoteobjects_wrapper.cpp +${QtRemoteObjects_GEN_DIR}/qtremoteobjects_wrapper.cpp ${QtRemoteObjects_GEN_DIR}/qremoteobjectsettingsstore_wrapper.cpp ${QtRemoteObjects_GEN_DIR}/qremoteobjectsourcelocationinfo_wrapper.cpp +${QtRemoteObjects_GEN_DIR}/qtroclientfactory_wrapper.cpp +${QtRemoteObjects_GEN_DIR}/qtroclientiodevice_wrapper.cpp +${QtRemoteObjects_GEN_DIR}/qtroiodevicebase_wrapper.cpp +${QtRemoteObjects_GEN_DIR}/qtroserverfactory_wrapper.cpp +${QtRemoteObjects_GEN_DIR}/qtroserveriodevice_wrapper.cpp # module is always needed ${QtRemoteObjects_GEN_DIR}/qtremoteobjects_module_wrapper.cpp @@ -29,11 +35,10 @@ set(QtRemoteObjects_include_dirs ${QtRemoteObjects_SOURCE_DIR} ${SHIBOKEN_INCLUDE_DIR} ${libpyside_SOURCE_DIR} ${SHIBOKEN_PYTHON_INCLUDE_DIR} - ${QtCore_GEN_DIR}) + ${QtCore_GEN_DIR} + ${QtNetwork_GEN_DIR}) set(QtRemoteObjects_libraries pyside6 - ${SHIBOKEN_PYTHON_LIBRARIES} - ${SHIBOKEN_LIBRARY} ${Qt${QT_MAJOR_VERSION}RemoteObjects_LIBRARIES}) set(QtRemoteObjects_deps QtCore QtNetwork) diff --git a/sources/pyside6/PySide6/QtRemoteObjects/typesystem_remoteobjects.xml b/sources/pyside6/PySide6/QtRemoteObjects/typesystem_remoteobjects.xml index 0926b9b2..86e4d909 100644 --- a/sources/pyside6/PySide6/QtRemoteObjects/typesystem_remoteobjects.xml +++ b/sources/pyside6/PySide6/QtRemoteObjects/typesystem_remoteobjects.xml @@ -3,20 +3,21 @@ // Copyright (C) 2019 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + + - + @@ -38,12 +39,17 @@ + + + + + - + diff --git a/sources/pyside6/PySide6/QtScxml/typesystem_scxml.xml b/sources/pyside6/PySide6/QtScxml/typesystem_scxml.xml index aeccf949..ba882057 100644 --- a/sources/pyside6/PySide6/QtScxml/typesystem_scxml.xml +++ b/sources/pyside6/PySide6/QtScxml/typesystem_scxml.xml @@ -3,7 +3,8 @@ // Copyright (C) 2018 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + diff --git a/sources/pyside6/PySide6/QtSensors/typesystem_sensors.xml b/sources/pyside6/PySide6/QtSensors/typesystem_sensors.xml index 924262f5..4efd0827 100644 --- a/sources/pyside6/PySide6/QtSensors/typesystem_sensors.xml +++ b/sources/pyside6/PySide6/QtSensors/typesystem_sensors.xml @@ -3,7 +3,8 @@ // Copyright (C) 2018 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + - + + + + + + + + @@ -61,6 +69,18 @@ + + + + + + + + + + + + @@ -81,6 +101,8 @@ + + diff --git a/sources/pyside6/PySide6/QtSerialPort/typesystem_serialport.xml b/sources/pyside6/PySide6/QtSerialPort/typesystem_serialport.xml index 929a8e90..0cbd3c7f 100644 --- a/sources/pyside6/PySide6/QtSerialPort/typesystem_serialport.xml +++ b/sources/pyside6/PySide6/QtSerialPort/typesystem_serialport.xml @@ -3,7 +3,8 @@ // Copyright (C) 2020 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + diff --git a/sources/pyside6/PySide6/QtSpatialAudio/CMakeLists.txt b/sources/pyside6/PySide6/QtSpatialAudio/CMakeLists.txt index 409759b6..966c0ffe 100644 --- a/sources/pyside6/PySide6/QtSpatialAudio/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtSpatialAudio/CMakeLists.txt @@ -28,12 +28,8 @@ set(QtSpatialAudio_include_dirs ${QtSpatialAudio_SOURCE_DIR} ${QtMultimedia_GEN_DIR}) set(QtSpatialAudio_libraries pyside6 - ${Qt${QT_MAJOR_VERSION}SpatialAudio_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Core_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Gui_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Network_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Multimedia_LIBRARIES} - ) + ${Qt${QT_MAJOR_VERSION}SpatialAudio_LIBRARIES}) + set(QtSpatialAudio_deps QtCore QtGui QtNetwork QtMultimedia) create_pyside_module(NAME QtSpatialAudio diff --git a/sources/pyside6/PySide6/QtSpatialAudio/typesystem_spatialaudio.xml b/sources/pyside6/PySide6/QtSpatialAudio/typesystem_spatialaudio.xml index 71f189b2..b9c2557a 100644 --- a/sources/pyside6/PySide6/QtSpatialAudio/typesystem_spatialaudio.xml +++ b/sources/pyside6/PySide6/QtSpatialAudio/typesystem_spatialaudio.xml @@ -3,7 +3,8 @@ // Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + diff --git a/sources/pyside6/PySide6/QtSql/CMakeLists.txt b/sources/pyside6/PySide6/QtSql/CMakeLists.txt index 2150eb3c..fedebe64 100644 --- a/sources/pyside6/PySide6/QtSql/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtSql/CMakeLists.txt @@ -35,13 +35,13 @@ set(QtSql_include_dirs ${QtSql_SOURCE_DIR} ${libpyside_SOURCE_DIR} ${QtCore_GEN_DIR} ${QtGui_GEN_DIR} - ${QtWidgets_GEN_DIR} - ) + ${QtWidgets_GEN_DIR}) + +# Link to QtWidgets to enable QSqlRelationalDelegate set(QtSql_libraries pyside6 - ${Qt${QT_MAJOR_VERSION}Core_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Gui_LIBRARIES} ${Qt${QT_MAJOR_VERSION}Widgets_LIBRARIES} ${Qt${QT_MAJOR_VERSION}Sql_LIBRARIES}) + set(QtSql_deps QtWidgets) create_pyside_module(NAME QtSql diff --git a/sources/pyside6/PySide6/QtSql/typesystem_sql.xml b/sources/pyside6/PySide6/QtSql/typesystem_sql.xml index 022a5ab0..70c3e6f6 100644 --- a/sources/pyside6/PySide6/QtSql/typesystem_sql.xml +++ b/sources/pyside6/PySide6/QtSql/typesystem_sql.xml @@ -3,7 +3,8 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + diff --git a/sources/pyside6/PySide6/QtStateMachine/CMakeLists.txt b/sources/pyside6/PySide6/QtStateMachine/CMakeLists.txt index 88a4805e..2b4cc348 100644 --- a/sources/pyside6/PySide6/QtStateMachine/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtStateMachine/CMakeLists.txt @@ -26,13 +26,11 @@ set(QtStateMachine_include_dirs ${QtStateMachine_SOURCE_DIR} ${Qt${QT_MAJOR_VERSION}Gui_INCLUDE_DIRS} ${libpyside_SOURCE_DIR} ${QtCore_GEN_DIR} - ${QtGui_GEN_DIR} - ) + ${QtGui_GEN_DIR}) + set(QtStateMachine_libraries pyside6 - ${Qt${QT_MAJOR_VERSION}Core_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Gui_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}StateMachine_LIBRARIES} - ) + ${Qt${QT_MAJOR_VERSION}StateMachine_LIBRARIES}) + set(QtStateMachine_deps QtGui) create_pyside_module(NAME QtStateMachine diff --git a/sources/pyside6/PySide6/QtStateMachine/typesystem_statemachine.xml b/sources/pyside6/PySide6/QtStateMachine/typesystem_statemachine.xml index bad0cc8e..0e29f240 100644 --- a/sources/pyside6/PySide6/QtStateMachine/typesystem_statemachine.xml +++ b/sources/pyside6/PySide6/QtStateMachine/typesystem_statemachine.xml @@ -3,7 +3,8 @@ // Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + diff --git a/sources/pyside6/PySide6/QtSvg/CMakeLists.txt b/sources/pyside6/PySide6/QtSvg/CMakeLists.txt index 60be42cb..5451380c 100644 --- a/sources/pyside6/PySide6/QtSvg/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtSvg/CMakeLists.txt @@ -6,6 +6,7 @@ project(QtSvg) set(QtSvg_SRC ${QtSvg_GEN_DIR}/qsvggenerator_wrapper.cpp ${QtSvg_GEN_DIR}/qsvgrenderer_wrapper.cpp +${QtSvg_GEN_DIR}/qtsvg_wrapper.cpp # module is always needed ${QtSvg_GEN_DIR}/qtsvg_module_wrapper.cpp ) @@ -17,13 +18,12 @@ set(QtSvg_include_dirs ${QtSvg_SOURCE_DIR} ${Qt${QT_MAJOR_VERSION}Svg_INCLUDE_DIRS} ${libpyside_SOURCE_DIR} ${QtCore_GEN_DIR} - ${QtGui_GEN_DIR} - ) + ${QtGui_GEN_DIR}) + set(QtSvg_libraries pyside6 - ${Qt${QT_MAJOR_VERSION}Core_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Gui_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Svg_LIBRARIES} - ) + ${Qt${QT_MAJOR_VERSION}Svg_LIBRARIES}) + + set(QtSvg_deps QtGui) create_pyside_module(NAME QtSvg diff --git a/sources/pyside6/PySide6/QtSvg/typesystem_svg.xml b/sources/pyside6/PySide6/QtSvg/typesystem_svg.xml index 834d8101..4803f4d9 100644 --- a/sources/pyside6/PySide6/QtSvg/typesystem_svg.xml +++ b/sources/pyside6/PySide6/QtSvg/typesystem_svg.xml @@ -3,10 +3,14 @@ // Copyright (C) 2020 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + + + + diff --git a/sources/pyside6/PySide6/QtSvgWidgets/CMakeLists.txt b/sources/pyside6/PySide6/QtSvgWidgets/CMakeLists.txt index 9203ac00..6bd3aedc 100644 --- a/sources/pyside6/PySide6/QtSvgWidgets/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtSvgWidgets/CMakeLists.txt @@ -21,16 +21,10 @@ set(QtSvgWidgets_include_dirs ${QtSvgWidgets_SOURCE_DIR} ${QtCore_GEN_DIR} ${QtGui_GEN_DIR} ${QtWidgets_GEN_DIR} - ${QtSvg_GEN_DIR} - ) + ${QtSvg_GEN_DIR}) set(QtSvgWidgets_libraries pyside6 - ${Qt${QT_MAJOR_VERSION}Core_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Gui_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Widgets_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Svg_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}SvgWidgets_LIBRARIES} - ) + ${Qt${QT_MAJOR_VERSION}SvgWidgets_LIBRARIES}) set(QtSvgWidgets_deps QtSvg QtWidgets) diff --git a/sources/pyside6/PySide6/QtSvgWidgets/typesystem_svgwidgets.xml b/sources/pyside6/PySide6/QtSvgWidgets/typesystem_svgwidgets.xml index ce7a02c6..36bda1ea 100644 --- a/sources/pyside6/PySide6/QtSvgWidgets/typesystem_svgwidgets.xml +++ b/sources/pyside6/PySide6/QtSvgWidgets/typesystem_svgwidgets.xml @@ -3,7 +3,8 @@ // Copyright (C) 2020 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + diff --git a/sources/pyside6/PySide6/QtTest/CMakeLists.txt b/sources/pyside6/PySide6/QtTest/CMakeLists.txt index e04903df..86bb2a73 100644 --- a/sources/pyside6/PySide6/QtTest/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtTest/CMakeLists.txt @@ -27,14 +27,15 @@ set(QtTest_include_dirs ${QtTest_SOURCE_DIR} ${libpyside_SOURCE_DIR} ${QtCore_GEN_DIR} ${QtGui_GEN_DIR} - ${QtWidgets_GEN_DIR} - ) + ${QtWidgets_GEN_DIR}) + +# Link to QtGui/QtWidgets to enable gui/widget-specific inline functions set(QtTest_libraries pyside6 ${Qt${QT_MAJOR_VERSION}Test_LIBRARIES} ${Qt${QT_MAJOR_VERSION}Widgets_LIBRARIES} ${Qt${QT_MAJOR_VERSION}Gui_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Core_LIBRARIES} - ) + ${Qt${QT_MAJOR_VERSION}Core_LIBRARIES}) + set(QtTest_deps QtWidgets) create_pyside_module(NAME QtTest diff --git a/sources/pyside6/PySide6/QtTest/typesystem_test.xml b/sources/pyside6/PySide6/QtTest/typesystem_test.xml index 084b18a1..2fc4f23c 100644 --- a/sources/pyside6/PySide6/QtTest/typesystem_test.xml +++ b/sources/pyside6/PySide6/QtTest/typesystem_test.xml @@ -3,7 +3,8 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + diff --git a/sources/pyside6/PySide6/QtTextToSpeech/CMakeLists.txt b/sources/pyside6/PySide6/QtTextToSpeech/CMakeLists.txt index 7f8db54b..9f0bd2fc 100644 --- a/sources/pyside6/PySide6/QtTextToSpeech/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtTextToSpeech/CMakeLists.txt @@ -19,9 +19,7 @@ set(QtTextToSpeech_include_dirs ${QtTextToSpeech_SOURCE_DIR} ${QtCore_GEN_DIR}) set(QtTextToSpeech_libraries pyside6 - ${Qt${QT_MAJOR_VERSION}Multimedia_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}TextToSpeech_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Core_LIBRARIES}) + ${Qt${QT_MAJOR_VERSION}TextToSpeech_LIBRARIES}) set(QtTextToSpeech_deps QtCore QtMultimedia) diff --git a/sources/pyside6/PySide6/QtTextToSpeech/typesystem_texttospeech.xml b/sources/pyside6/PySide6/QtTextToSpeech/typesystem_texttospeech.xml index 985c4994..70751e9d 100644 --- a/sources/pyside6/PySide6/QtTextToSpeech/typesystem_texttospeech.xml +++ b/sources/pyside6/PySide6/QtTextToSpeech/typesystem_texttospeech.xml @@ -3,7 +3,8 @@ // Copyright (C) 2017 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + diff --git a/sources/pyside6/PySide6/QtUiTools/CMakeLists.txt b/sources/pyside6/PySide6/QtUiTools/CMakeLists.txt index b7385b87..e16d2d58 100644 --- a/sources/pyside6/PySide6/QtUiTools/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtUiTools/CMakeLists.txt @@ -25,15 +25,12 @@ set(QtUiTools_include_dirs ${QtUiTools_SOURCE_DIR} ${plugins_SOURCE_DIR} ${QtCore_GEN_DIR} ${QtGui_GEN_DIR} - ${QtWidgets_GEN_DIR} - ) + ${QtWidgets_GEN_DIR}) + set(QtUiTools_libraries pyside6 uiplugin - ${Qt${QT_MAJOR_VERSION}UiTools_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Core_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Gui_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Widgets_LIBRARIES} - ) + ${Qt${QT_MAJOR_VERSION}UiTools_LIBRARIES}) + set(QtUiTools_deps QtWidgets) configure_file("${QtUiTools_SOURCE_DIR}/QtUiTools_global.pre.h.in" diff --git a/sources/pyside6/PySide6/QtUiTools/typesystem_uitools.xml b/sources/pyside6/PySide6/QtUiTools/typesystem_uitools.xml index a67dd6f6..9cfa176c 100644 --- a/sources/pyside6/PySide6/QtUiTools/typesystem_uitools.xml +++ b/sources/pyside6/PySide6/QtUiTools/typesystem_uitools.xml @@ -4,7 +4,8 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + @@ -14,38 +15,10 @@ - - Registers a Python created custom widget to QUiLoader, so it can be recognized when - loading a `.ui` file. The custom widget type is passed via the ``customWidgetType`` argument. - This is needed when you want to override a virtual method of some widget in the interface, - since duck punching will not work with widgets created by QUiLoader based on the contents - of the `.ui` file. - - (Remember that `duck punching virtual methods is an invitation for your own demise! - <https://doc.qt.io/qtforpython/shiboken6/wordsofadvice.html#duck-punching-and-virtual-methods>`_) - - Let's see an obvious example. If you want to create a new widget it's probable you'll end up - overriding :class:`~PySide6.QtGui.QWidget`'s :meth:`~PySide6.QtGui.QWidget.paintEvent` method. - - .. code-block:: python - - class Circle(QWidget): - def paintEvent(self, event): - with QPainter(self) as painter: - painter.setPen(self.pen) - painter.setBrush(QBrush(self.color)) - painter.drawEllipse(event.rect().center(), 20, 20) - - # ... - - loader = QUiLoader() - loader.registerCustomWidget(Circle) - circle = loader.load('circle.ui') - circle.show() - - # ... - - + + @@ -107,6 +80,8 @@ --> + diff --git a/sources/pyside6/PySide6/QtWebChannel/CMakeLists.txt b/sources/pyside6/PySide6/QtWebChannel/CMakeLists.txt index ecb2caf5..4c06edd2 100644 --- a/sources/pyside6/PySide6/QtWebChannel/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtWebChannel/CMakeLists.txt @@ -15,12 +15,11 @@ set(QtWebChannel_include_dirs ${QtWebChannel_SOURCE_DIR} ${Qt${QT_MAJOR_VERSION}Core_INCLUDE_DIRS} ${Qt${QT_MAJOR_VERSION}WebChannel_INCLUDE_DIRS} ${libpyside_SOURCE_DIR} - ${QtCore_GEN_DIR} - ) -set(QtWebChannel_libraries pyside6 - ${Qt${QT_MAJOR_VERSION}WebChannel_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Core_LIBRARIES} - ) + ${QtCore_GEN_DIR}) + +set(QtWebChannel_libraries pyside6 + ${Qt${QT_MAJOR_VERSION}WebChannel_LIBRARIES}) + set(QtWebChannel_deps QtCore) create_pyside_module(NAME QtWebChannel diff --git a/sources/pyside6/PySide6/QtWebChannel/typesystem_webchannel.xml b/sources/pyside6/PySide6/QtWebChannel/typesystem_webchannel.xml index 75a51853..c08a9b18 100644 --- a/sources/pyside6/PySide6/QtWebChannel/typesystem_webchannel.xml +++ b/sources/pyside6/PySide6/QtWebChannel/typesystem_webchannel.xml @@ -3,7 +3,8 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + diff --git a/sources/pyside6/PySide6/QtWebEngineCore/CMakeLists.txt b/sources/pyside6/PySide6/QtWebEngineCore/CMakeLists.txt index 3f376fd4..0cdaf2f9 100644 --- a/sources/pyside6/PySide6/QtWebEngineCore/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtWebEngineCore/CMakeLists.txt @@ -5,17 +5,25 @@ project(QtWebEngineCore) set(QtWebEngineCore_SRC ${QtWebEngineCore_GEN_DIR}/qwebenginecertificateerror_wrapper.cpp +${QtWebEngineCore_GEN_DIR}/qwebengineclientcertificateselection_wrapper.cpp +${QtWebEngineCore_GEN_DIR}/qwebengineclientcertificatestore_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebenginecontextmenurequest_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebenginecookiestore_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebenginecookiestore_filterrequest_wrapper.cpp +# FIXME ${QtWebEngineCore_GEN_DIR}/qwebenginedesktopmediarequest_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebenginedownloadrequest_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebenginefilesystemaccessrequest_wrapper.cpp +${QtWebEngineCore_GEN_DIR}/qwebenginefindtextresult_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebenginefullscreenrequest_wrapper.cpp +${QtWebEngineCore_GEN_DIR}/qwebengineglobalsettings_wrapper.cpp +${QtWebEngineCore_GEN_DIR}/qwebengineglobalsettings_dnsmode_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebenginehistory_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebenginehistoryitem_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebenginehistorymodel_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebenginehttprequest_wrapper.cpp +${QtWebEngineCore_GEN_DIR}/qwebenginenavigationrequest_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebengineloadinginfo_wrapper.cpp +# FIXME ${QtWebEngineCore_GEN_DIR}/qwebenginemediasourcemodel_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebenginenewwindowrequest_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebenginenotification_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebenginepage_wrapper.cpp @@ -25,12 +33,13 @@ ${QtWebEngineCore_GEN_DIR}/qwebengineregisterprotocolhandlerrequest_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebenginescript_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebenginescriptcollection_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebenginesettings_wrapper.cpp -${QtWebEngineCore_GEN_DIR}/qwebenginefindtextresult_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebengineurlrequestinfo_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebengineurlrequestinterceptor_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebengineurlrequestjob_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebengineurlscheme_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebengineurlschemehandler_wrapper.cpp +${QtWebEngineCore_GEN_DIR}/qwebenginewebauthpinrequest_wrapper.cpp +${QtWebEngineCore_GEN_DIR}/qwebenginewebauthuxrequest_wrapper.cpp # module is always needed ${QtWebEngineCore_GEN_DIR}/qtwebenginecore_module_wrapper.cpp ) @@ -50,17 +59,10 @@ set(QtWebEngineCore_include_dirs ${QtWidgets_GEN_DIR} ${QtNetwork_GEN_DIR} ${QtPrintSupport_GEN_DIR} - ${QtWebChannel_GEN_DIR} - ) + ${QtWebChannel_GEN_DIR}) + set(QtWebEngineCore_libraries pyside6 - ${Qt${QT_MAJOR_VERSION}WebEngineCore_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Core_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Gui_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Widgets_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Network_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}PrintSupport_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}WebChannel_LIBRARIES} - ) + ${Qt${QT_MAJOR_VERSION}WebEngineCore_LIBRARIES}) set(QtWebEngineCore_deps QtCore QtGui QtNetwork QtPrintSupport QtWebChannel) diff --git a/sources/pyside6/PySide6/QtWebEngineCore/typesystem_webenginecore.xml b/sources/pyside6/PySide6/QtWebEngineCore/typesystem_webenginecore.xml index ff88b358..da9259cc 100644 --- a/sources/pyside6/PySide6/QtWebEngineCore/typesystem_webenginecore.xml +++ b/sources/pyside6/PySide6/QtWebEngineCore/typesystem_webenginecore.xml @@ -3,7 +3,8 @@ // Copyright (C) 2018 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + @@ -16,7 +17,12 @@ + + + + + + + + + @@ -71,6 +82,16 @@ + + + + + + + @@ -89,6 +110,8 @@ + @@ -123,7 +146,7 @@ - + @@ -147,6 +170,22 @@ + + + + + + + + + + + + + diff --git a/sources/pyside6/PySide6/QtWebEngineQuick/CMakeLists.txt b/sources/pyside6/PySide6/QtWebEngineQuick/CMakeLists.txt index 63bc14f4..d87dc55a 100644 --- a/sources/pyside6/PySide6/QtWebEngineQuick/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtWebEngineQuick/CMakeLists.txt @@ -14,22 +14,26 @@ set(QtWebEngineQuick_include_dirs ${QtWebEngineQuick_SOURCE_DIR} ${QtWebEngineQuick_BINARY_DIR} ${Qt${QT_MAJOR_VERSION}Core_INCLUDE_DIRS} + ${Qt${QT_MAJOR_VERSION}Gui_INCLUDE_DIRS} + ${Qt${QT_MAJOR_VERSION}Widgets_INCLUDE_DIRS} ${Qt${QT_MAJOR_VERSION}Network_INCLUDE_DIRS} + ${Qt${QT_MAJOR_VERSION}PrintSupport_INCLUDE_DIRS} ${Qt${QT_MAJOR_VERSION}Qml_INCLUDE_DIRS} + ${Qt${QT_MAJOR_VERSION}WebChannel_INCLUDE_DIRS} ${libpyside_SOURCE_DIR} ${QtCore_GEN_DIR} + ${QtGui_GEN_DIR} + ${QtWidgets_GEN_DIR} ${QtNetwork_GEN_DIR} + ${QtWebEngineCore_GEN_DIR} + ${QtPrintSupport_GEN_DIR} ${QtQml_GEN_DIR} - ) + ${QtWebChannel_GEN_DIR}) set(QtWebEngineQuick_libraries pyside6 - ${Qt${QT_MAJOR_VERSION}Core_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Network_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Qml_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}WebEngineQuick_LIBRARIES} - ) + ${Qt${QT_MAJOR_VERSION}WebEngineQuick_LIBRARIES}) -set(QtWebEngineQuick_deps QtQml QtNetwork QtCore) +set(QtWebEngineQuick_deps QtQml QtWebEngineCore) create_pyside_module(NAME QtWebEngineQuick INCLUDE_DIRS QtWebEngineQuick_include_dirs diff --git a/sources/pyside6/PySide6/QtWebEngineQuick/typesystem_webenginequick.xml b/sources/pyside6/PySide6/QtWebEngineQuick/typesystem_webenginequick.xml index 72a31deb..2e7d2233 100644 --- a/sources/pyside6/PySide6/QtWebEngineQuick/typesystem_webenginequick.xml +++ b/sources/pyside6/PySide6/QtWebEngineQuick/typesystem_webenginequick.xml @@ -3,8 +3,10 @@ // Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + + diff --git a/sources/pyside6/PySide6/QtWebEngineWidgets/CMakeLists.txt b/sources/pyside6/PySide6/QtWebEngineWidgets/CMakeLists.txt index de8c41f5..9da97243 100644 --- a/sources/pyside6/PySide6/QtWebEngineWidgets/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtWebEngineWidgets/CMakeLists.txt @@ -27,17 +27,10 @@ set(QtWebEngineWidgets_include_dirs ${QtNetwork_GEN_DIR} ${QtWebEngineCore_GEN_DIR} ${QtPrintSupport_GEN_DIR} - ${QtWebChannel_GEN_DIR} - ) -set(QtWebEngineWidgets_libraries pyside6 - ${Qt${QT_MAJOR_VERSION}WebEngineWidgets_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Network_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Widgets_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Gui_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Core_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}PrintSupport_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}WebChannel_LIBRARIES} - ) + ${QtWebChannel_GEN_DIR}) + +set(QtWebEngineWidgets_libraries pyside6 + ${Qt${QT_MAJOR_VERSION}WebEngineWidgets_LIBRARIES}) set(QtWebEngineWidgets_deps QtGui QtWidgets QtNetwork QtPrintSupport QtWebChannel QtWebEngineCore) diff --git a/sources/pyside6/PySide6/QtWebEngineWidgets/typesystem_webenginewidgets.xml b/sources/pyside6/PySide6/QtWebEngineWidgets/typesystem_webenginewidgets.xml index fb5e6d37..61874856 100644 --- a/sources/pyside6/PySide6/QtWebEngineWidgets/typesystem_webenginewidgets.xml +++ b/sources/pyside6/PySide6/QtWebEngineWidgets/typesystem_webenginewidgets.xml @@ -3,7 +3,8 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + diff --git a/sources/pyside6/PySide6/QtWebSockets/CMakeLists.txt b/sources/pyside6/PySide6/QtWebSockets/CMakeLists.txt index b51b5d10..5ff07555 100644 --- a/sources/pyside6/PySide6/QtWebSockets/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtWebSockets/CMakeLists.txt @@ -22,13 +22,10 @@ set(QtWebSockets_include_dirs ${QtWebSockets_SOURCE_DIR} ${libpyside_SOURCE_DIR} ${QtCore_GEN_DIR} ${QtWebSockets_GEN_DIR} - ${QtNetwork_GEN_DIR} - ) -set(QtWebSockets_libraries pyside6 - ${Qt${QT_MAJOR_VERSION}WebSockets_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Network_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Core_LIBRARIES} - ) + ${QtNetwork_GEN_DIR}) + +set(QtWebSockets_libraries pyside6 + ${Qt${QT_MAJOR_VERSION}WebSockets_LIBRARIES}) set(QtWebSockets_deps QtNetwork) diff --git a/sources/pyside6/PySide6/QtWebSockets/typesystem_websockets.xml b/sources/pyside6/PySide6/QtWebSockets/typesystem_websockets.xml index 914c8ce8..2d8d9589 100644 --- a/sources/pyside6/PySide6/QtWebSockets/typesystem_websockets.xml +++ b/sources/pyside6/PySide6/QtWebSockets/typesystem_websockets.xml @@ -3,7 +3,8 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + diff --git a/sources/pyside6/PySide6/QtWidgets/CMakeLists.txt b/sources/pyside6/PySide6/QtWidgets/CMakeLists.txt index 3ce30d46..1a77181f 100644 --- a/sources/pyside6/PySide6/QtWidgets/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtWidgets/CMakeLists.txt @@ -116,6 +116,7 @@ ${QtWidgets_GEN_DIR}/qprogressdialog_wrapper.cpp ${QtWidgets_GEN_DIR}/qproxystyle_wrapper.cpp ${QtWidgets_GEN_DIR}/qpushbutton_wrapper.cpp ${QtWidgets_GEN_DIR}/qradiobutton_wrapper.cpp +${QtWidgets_GEN_DIR}/qrhiwidget_wrapper.cpp ${QtWidgets_GEN_DIR}/qrubberband_wrapper.cpp ${QtWidgets_GEN_DIR}/qscrollarea_wrapper.cpp ${QtWidgets_GEN_DIR}/qscrollbar_wrapper.cpp @@ -209,13 +210,11 @@ set(QtWidgets_include_dirs ${QtWidgets_SOURCE_DIR} ${Qt${QT_MAJOR_VERSION}Widgets_INCLUDE_DIRS} ${libpyside_SOURCE_DIR} ${QtCore_GEN_DIR} - ${QtGui_GEN_DIR} - ) + ${QtGui_GEN_DIR}) + set(QtWidgets_libraries pyside6 - ${Qt${QT_MAJOR_VERSION}Core_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Gui_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}Widgets_LIBRARIES} - ) + ${Qt${QT_MAJOR_VERSION}Widgets_LIBRARIES}) + set(QtWidgets_deps QtGui) create_pyside_module(NAME QtWidgets diff --git a/sources/pyside6/PySide6/QtWidgets/typesystem_widgets.xml b/sources/pyside6/PySide6/QtWidgets/typesystem_widgets.xml index fabd87d2..93a7151f 100644 --- a/sources/pyside6/PySide6/QtWidgets/typesystem_widgets.xml +++ b/sources/pyside6/PySide6/QtWidgets/typesystem_widgets.xml @@ -3,7 +3,8 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + diff --git a/sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml b/sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml index 956dedbb..0669c18f 100644 --- a/sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml +++ b/sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml @@ -3,7 +3,8 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + @@ -43,8 +44,10 @@ + + - @@ -52,45 +55,54 @@ - + - + - + - + - + - + - + - + - + - + @@ -101,29 +113,35 @@ - + - + - + - + - + - + @@ -131,36 +149,41 @@ - + - + - + - + - + - + - + @@ -468,7 +491,9 @@ - + + + @@ -1066,12 +1091,14 @@ - + - + - + @@ -1082,14 +1109,21 @@ - - - - - - - - + + + + + + + + @@ -1181,7 +1215,8 @@ - + @@ -1315,7 +1350,8 @@ - + @@ -2885,7 +2921,7 @@ - + @@ -3345,6 +3381,12 @@ + + + + + + diff --git a/sources/pyside6/PySide6/QtXml/CMakeLists.txt b/sources/pyside6/PySide6/QtXml/CMakeLists.txt index 82001cc6..b0e4630d 100644 --- a/sources/pyside6/PySide6/QtXml/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtXml/CMakeLists.txt @@ -9,6 +9,7 @@ ${QtXml_GEN_DIR}/qdomcdatasection_wrapper.cpp ${QtXml_GEN_DIR}/qdomcharacterdata_wrapper.cpp ${QtXml_GEN_DIR}/qdomcomment_wrapper.cpp ${QtXml_GEN_DIR}/qdomdocument_wrapper.cpp +${QtXml_GEN_DIR}/qdomdocument_parseresult_wrapper.cpp ${QtXml_GEN_DIR}/qdomdocumentfragment_wrapper.cpp ${QtXml_GEN_DIR}/qdomdocumenttype_wrapper.cpp ${QtXml_GEN_DIR}/qdomelement_wrapper.cpp diff --git a/sources/pyside6/PySide6/QtXml/typesystem_xml.xml b/sources/pyside6/PySide6/QtXml/typesystem_xml.xml index 6b850f2c..3661a67b 100644 --- a/sources/pyside6/PySide6/QtXml/typesystem_xml.xml +++ b/sources/pyside6/PySide6/QtXml/typesystem_xml.xml @@ -3,7 +3,8 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - + @@ -18,6 +19,9 @@ + + + diff --git a/sources/pyside6/PySide6/doc/qtqml_functions.rst b/sources/pyside6/PySide6/doc/qtqml_functions.rst new file mode 100644 index 00000000..31801b24 --- /dev/null +++ b/sources/pyside6/PySide6/doc/qtqml_functions.rst @@ -0,0 +1,152 @@ +// @snippet qmlregistersingletoninstance +.. py:function:: qmlRegisterSingletonInstance(pytype: type,\ + uri: str,\ + versionMajor: int,\ + versionMinor: int,\ + typeName: str,\ + instanceObject: object) -> int + + :param type pytype: Python class + :param str uri: uri to use while importing the component in QML + :param int versionMajor: major version + :param int versionMinor: minor version + :param str typeName: name exposed to QML + :param object instanceObject: singleton object to be registered + :return: int (the QML type id) + +This function registers a singleton Python object *instanceObject*, with a +particular *uri* and *typeName*. Its version is a combination of *versionMajor* +and *versionMinor*. Use this function to register an object of the given type +*pytype* as a singleton type. +// @snippet qmlregistersingletoninstance + +// @snippet qmlregistersingletontype_qobject_nocallback +.. py:function:: qmlRegisterSingletonType(pytype: type, uri: str, versionMajor: int, versionMinor: int, typeName: str) -> int + + :param type pytype: Python class + :param str uri: uri to use while importing the component in QML + :param int versionMajor: major version + :param int versionMinor: minor version + :param str typeName: name exposed to QML + :return: int (the QML type id) + +This function registers a Python type as a singleton in the QML system. + +Alternatively, the :ref:`QmlSingleton` decorator can be used. +// @snippet qmlregistersingletontype_qobject_nocallback + +// @snippet qmlregistersingletontype_qobject_callback +.. py:function:: qmlRegisterSingletonType(pytype: type, uri: str, versionMajor: int, versionMinor: int, typeName: str, callback: object) -> int + + :param type pytype: Python class + :param str uri: uri to use while importing the component in QML + :param int versionMajor: major version + :param int versionMinor: minor version + :param str typeName: name exposed to QML + :param object callback: Python callable (to handle Python type) + :return: int (the QML type id) + +This function registers a Python type as a singleton in the QML system using +the provided callback (which gets a QQmlEngine as a parameter) to generate the +singleton. +// @snippet qmlregistersingletontype_qobject_callback + +// @snippet qmlregistersingletontype_qjsvalue +.. py:function:: qmlRegisterSingletonType(uri: str, versionMajor: int, versionMinor: int, typeName: str, callback: object) -> int + + :param str uri: uri to use while importing the component in QML + :param int versionMajor: major version + :param int versionMinor: minor version + :param str typeName: name exposed to QML + :param object callback: Python callable (to handle QJSValue) + :return: int (the QML type id) + +This function registers a QJSValue as a singleton in the QML system using the +provided callback (which gets a QQmlEngine as a parameter) to generate the +singleton. +// @snippet qmlregistersingletontype_qjsvalue + +// @snippet qmlregistertype +.. py:function:: qmlRegisterType(pytype: type, uri: str, versionMajor: int, versionMinor: int, qmlName: str) -> int + + :param type pytype: Python class + :param str uri: uri to use while importing the component in QML + :param int versionMajor: major version + :param int versionMinor: minor version + :param str qmlName: name exposed to QML + :return: int (the QML type id) + +This function registers the Python *type* in the QML system with the name +*qmlName*, in the library imported from *uri* having the version number +composed from *versionMajor* and *versionMinor*. For example, this registers a +Python class 'MySliderItem' as a QML type named 'Slider' for version '1.0' of a +module called 'com.mycompany.qmlcomponents': + + :: + + qmlRegisterType(MySliderItem, "com.mycompany.qmlcomponents", 1, 0, "Slider") + +Once this is registered, the type can be used in QML by importing the specified +module name and version number: + + :: + + import com.mycompany.qmlcomponents 1.0 + + Slider { ... } + +Note that it's perfectly reasonable for a library to register types to older +versions than the actual version of the library. Indeed, it is normal for the +new library to allow QML written to previous versions to continue to work, even +if more advanced versions of some of its types are available. +// @snippet qmlregistertype + +// @snippet qmlregisteruncreatabletype +.. py:function:: qmlRegisterUncreatableType(pytype: type, uri: str, versionMajor: int, versionMinor: int, qmlName: str, noCreationReason: str) -> int + + :param type pytype: Python class + :param str uri: uri to use while importing the component in QML + :param int versionMajor: major version + :param int versionMinor: minor version + :param str qmlName: name exposed to QML + :param str noCreationReason: Error message shown when trying to create the QML type + :return: int (the QML type id) + +This function registers the Python *type* in the QML system as an uncreatable +type with the name *qmlName*, in the library imported from *uri* having the +version number composed from *versionMajor* and *versionMinor*, showing +*noCreationReason* as an error message when creating the type is attempted. For +example, this registers a Python class 'MySliderItem' as a QML type named +'Slider' for version '1.0' of a module called 'com.mycompany.qmlcomponents': + + :: + qmlRegisterUncreatableType(MySliderItem, "com.mycompany.qmlcomponents", 1, 0, "Slider", "Slider cannot be created.") + +Note that it's perfectly reasonable for a library to register types to older +versions than the actual version of the library. Indeed, it is normal for the +new library to allow QML written to previous versions to continue to work, even +if more advanced versions of some of its types are available. + +Alternatively, the :ref:`QmlUncreatable` decorator can be used. +// @snippet qmlregisteruncreatabletype + +// @snippet qqmlengine-singletoninstance-qmltypeid +Returns the instance of a singleton type that was registered under qmlTypeId. +For ``QObject``-derived singleton types, the ``QObject`` instance is returned, +otherwise a ``QJSValue`` or ``None``. + +It is recommended to store the QML type id, e.g. as a static member in the +singleton class. The lookup via qmlTypeId() is costly. +// @snippet qqmlengine-singletoninstance-qmltypeid + +// @snippet qqmlengine-singletoninstance-typename Returns the instance of a +singleton type named typeName from the module specified by uri. +For ``QObject``-derived singleton types, the ``QObject`` instance is returned, +otherwise a ``QJSValue`` or ``None``. + +This method can be used as an alternative to calling qmlTypeId followed by the +id based overload of singletonInstance. This is convenient when one only needs +to do a one time setup of a singleton; if repeated access to the singleton is +required, caching its typeId will allow faster subsequent access via the +type-id based overload. +// @snippet qqmlengine-singletoninstance-typename diff --git a/sources/pyside6/PySide6/doc/qtquicktest.rst b/sources/pyside6/PySide6/doc/qtquicktest.rst new file mode 100644 index 00000000..9df2af07 --- /dev/null +++ b/sources/pyside6/PySide6/doc/qtquicktest.rst @@ -0,0 +1,62 @@ +// @snippet quick_test_main_documentation + +Sets up the entry point for a Qt Quick Test application. +The ``name`` argument uniquely identifies this set of tests. + +``sys.argv`` should be passed to the ``argv`` argument to ensure +propagation of the command line arguments. + +.. note:: The function assumes that your test sources are in the current + directory, unless the ``QUICK_TEST_SOURCE_DIR`` environment + variable is set or a directory is passed in ``dir``. + +The following snippet demonstrates the use of this function: + +.. code-block:: Python + + import sys + from PySide6.QtQuickTest import QUICK_TEST_MAIN + + ex = QUICK_TEST_MAIN("example", sys.argv) + sys.exit(ex) + + +// @snippet quick_test_main_documentation + +// @snippet quick_test_main_with_setup_documentation + +Sets up the entry point for a Qt Quick Test application. +The ``name`` argument uniquely identifies this set of tests. + +``sys.argv`` should be passed to the ``argv`` argument to ensure +propagation of the command line arguments. + +This function is identical to ``QUICK_TEST_MAIN()``, except that it takes an +additional argument ``setup``, the type of a ``QObject``-derived +class which will be instantiated. With this class, it is possible to define +additional setup code to execute before running the QML test. + +The following snippet demonstrates the use of this function: + +.. code-block:: Python + + import sys + from PySide6.QtQuickTest import QUICK_TEST_MAIN_WITH_SETUP + + class CustomTestSetup(QObject): + def __init__(self, parent=None): + super().__init__(parent) + + @Slot(QQmlEngine) + def qmlEngineAvailable(self, qmlEngine): + pass + + ex = QUICK_TEST_MAIN_WITH_SETUP("qquicktestsetup", CustomTestSetup, sys.argv) + sys.exit(ex) + + +.. note:: The function assumes that your test sources are in the current + directory, unless the ``QUICK_TEST_SOURCE_DIR`` environment + variable is set or a directory is passed in ``dir``. + +// @snippet quick_test_main_with_setup_documentation diff --git a/sources/pyside6/PySide6/doc/qtuitools.rst b/sources/pyside6/PySide6/doc/qtuitools.rst new file mode 100644 index 00000000..a8856f1a --- /dev/null +++ b/sources/pyside6/PySide6/doc/qtuitools.rst @@ -0,0 +1,68 @@ +// @snippet quiloader-registercustomwidget +Registers a Python created custom widget to QUiLoader, so it can be recognized +when loading a `.ui` file. The custom widget type is passed via the +``customWidgetType`` argument. This is needed when you want to override a +virtual method of some widget in the interface, since duck punching will not +work with widgets created by QUiLoader based on the contents of the `.ui` file. + +(Remember that +`duck punching virtual methods is an invitation for your own demise! `_) + +Let's see an obvious example. If you want to create a new widget it's probable you'll end up +overriding :class:`~PySide6.QtGui.QWidget`'s :meth:`~PySide6.QtGui.QWidget.paintEvent` method. + +.. code-block:: python + + class Circle(QWidget): + def paintEvent(self, event): + with QPainter(self) as painter: + painter.setPen(self.pen) + painter.setBrush(QBrush(self.color)) + painter.drawEllipse(event.rect().center(), 20, 20) + + # ... + + loader = QUiLoader() + loader.registerCustomWidget(Circle) + circle = loader.load('circle.ui') + circle.show() + + # ... +// @snippet quiloader-registercustomwidget + +// @snippet loaduitype +.. currentmodule:: PySide6.QtUiTools + +loadUiType +*********** +.. py:function:: loadUiType(uifile: str) -> tuple(object, object) + + :param str uifile: The name of the `.ui` file + :return: tuple(object, object) + +This function generates and loads a `.ui` file at runtime, and it returns +a `tuple` containing the reference to the Python class, and the base class. + +We recommend not to use this approach as the workflow should be to generate a Python file +from the `.ui` file, and then import and load it to use it, but we do understand that +there are some corner cases when such functionality is required. + +The internal process relies on `uic` being in the PATH. +The `pyside6-uic` wrapper uses a shipped `uic` that is located in the +`site-packages/PySide6/uic`, so PATH needs to be updated to use that if there +is no `uic` in the system. + +A simple use case is:: + + from PySide6.QtUiTools import loadUiType + + generated_class, base_class = loadUiType("themewidget.ui") + # the values will be: + # (, ) + + widget = base_class() + form = generated_class() + form.setupUi(widget) + # form.a_widget_member.a_method_of_member() + widget.show() +// @snippet loaduitype diff --git a/sources/pyside6/PySide6/glue/qtcore.cpp b/sources/pyside6/PySide6/glue/qtcore.cpp index 94dc915a..0c206db7 100644 --- a/sources/pyside6/PySide6/glue/qtcore.cpp +++ b/sources/pyside6/PySide6/glue/qtcore.cpp @@ -84,11 +84,9 @@ static PyObject *convertToPrimitiveType(const QVariant &out, int metaTypeId) return PyFloat_FromDouble(out.toFloat()); case QMetaType::Bool: if (out.toBool()) { - Py_INCREF(Py_True); - return Py_True; + Py_RETURN_TRUE; } - Py_INCREF(Py_False); - return Py_False; + Py_RETURN_FALSE; default: break; } @@ -269,6 +267,30 @@ static QVariant QVariant_convertToVariantList(PyObject *list) } return QVariant(lst); } + +using SpecificConverter = Shiboken::Conversions::SpecificConverter; + +static std::optional converterForQtType(const char *typeNameC) +{ + // Fix typedef "QGenericMatrix<3,3,float>" -> QMatrix3x3". The reverse + // conversion happens automatically in QMetaType::fromName() in + // QVariant_resolveMetaType(). + QByteArrayView typeNameV(typeNameC); + if (typeNameV.startsWith("QGenericMatrix<") && typeNameV.endsWith(",float>")) { + QByteArray typeName = typeNameV.toByteArray(); + typeName.remove(1, 7); + typeName.remove(7, 1); // '<' + typeName.chop(7); + typeName.replace(',', 'x'); + SpecificConverter matrixConverter(typeName.constData()); + if (matrixConverter) + return matrixConverter; + } + SpecificConverter converter(typeNameC); + if (converter) + return converter; + return std::nullopt; +} // @snippet qvariant-conversion // @snippet qt-qabs @@ -284,6 +306,18 @@ PySide::addPostRoutine(%1); qAddPostRoutine(PySide::globalPostRoutineCallback); // @snippet qt-qaddpostroutine +// @snippet qcompress-buffer +auto *ptr = reinterpret_cast(Shiboken::Buffer::getPointer(%PYARG_1)); +QByteArray compressed = %FUNCTION_NAME(ptr, %2, %3); +%PYARG_0 = %CONVERTTOPYTHON[QByteArray](compressed); +// @snippet qcompress-buffer + +// @snippet quncompress-buffer +auto *ptr = reinterpret_cast(Shiboken::Buffer::getPointer(%PYARG_1)); +QByteArray uncompressed = %FUNCTION_NAME(ptr, %2); +%PYARG_0 = %CONVERTTOPYTHON[QByteArray](uncompressed); +// @snippet quncompress-buffer + // @snippet qt-version QList version = QByteArray(qVersion()).split('.'); PyObject *pyQtVersion = PyTuple_New(3); @@ -383,10 +417,10 @@ PySide::Feature::init(); // @snippet qt-init-feature // @snippet qt-pysideinit -Shiboken::Conversions::registerConverterName(SbkPySide6_QtCoreTypeConverters[SBK_QSTRING_IDX], "unicode"); -Shiboken::Conversions::registerConverterName(SbkPySide6_QtCoreTypeConverters[SBK_QSTRING_IDX], "str"); -Shiboken::Conversions::registerConverterName(SbkPySide6_QtCoreTypeConverters[SBK_QTCORE_QLIST_QVARIANT_IDX], "QVariantList"); -Shiboken::Conversions::registerConverterName(SbkPySide6_QtCoreTypeConverters[SBK_QTCORE_QMAP_QSTRING_QVARIANT_IDX], "QVariantMap"); +Shiboken::Conversions::registerConverterName(SbkPySide6_QtCoreTypeConverters[SBK_QString_IDX], "unicode"); +Shiboken::Conversions::registerConverterName(SbkPySide6_QtCoreTypeConverters[SBK_QString_IDX], "str"); +Shiboken::Conversions::registerConverterName(SbkPySide6_QtCoreTypeConverters[SBK_QtCore_QList_QVariant_IDX], "QVariantList"); +Shiboken::Conversions::registerConverterName(SbkPySide6_QtCoreTypeConverters[SBK_QtCore_QMap_QString_QVariant_IDX], "QVariantMap"); PySide::registerInternalQtConf(); PySide::init(module); @@ -508,31 +542,6 @@ if (!PyDateTimeAPI) %PYARG_0 = PyDateTime_FromDateAndTime(date.year(), date.month(), date.day(), time.hour(), time.minute(), time.second(), time.msec()*1000); // @snippet qdatetime-topython -// @snippet qpoint -namespace PySide { - template<> inline Py_ssize_t hash(const QPoint &v) { - return qHash(qMakePair(v.x(), v.y())); - } -}; -// @snippet qpoint - -// @snippet qrect -namespace PySide { - template<> inline Py_ssize_t hash(const QRect &r) { - const int v[4] = {r.x(), r.y(), r.width(), r.height()}; - return qHashRange(v, v + 4); - } -}; -// @snippet qrect - -// @snippet qsize -namespace PySide { - template<> inline Py_ssize_t hash(const QSize &v) { - return qHash(qMakePair(v.width(), v.height())); - } -}; -// @snippet qsize - // @snippet qtime-topython if (!PyDateTimeAPI) PyDateTime_IMPORT; @@ -621,20 +630,16 @@ if (ret == nullptr) { // @snippet qbytearray-mgetitem if (PyIndex_Check(_key)) { const Py_ssize_t _i = PyNumber_AsSsize_t(_key, PyExc_IndexError); - if (_i < 0 || _i >= %CPPSELF.size()) { - PyErr_SetString(PyExc_IndexError, "index out of bounds"); - return nullptr; - } + if (_i < 0 || _i >= %CPPSELF.size()) + return PyErr_Format(PyExc_IndexError, "index out of bounds"); char res[2] = {%CPPSELF.at(_i), '\0'}; return PyBytes_FromStringAndSize(res, 1); } -if (PySlice_Check(_key) == 0) { - PyErr_Format(PyExc_TypeError, +if (PySlice_Check(_key) == 0) + return PyErr_Format(PyExc_TypeError, "list indices must be integers or slices, not %.200s", Py_TYPE(_key)->tp_name); - return nullptr; -} Py_ssize_t start, stop, step, slicelength; if (PySlice_GetIndicesEx(_key, %CPPSELF.size(), &start, &stop, &step, &slicelength) < 0) @@ -660,6 +665,7 @@ return %CONVERTTOPYTHON[QByteArray](ba); // @snippet qbytearray-mgetitem // @snippet qbytearray-msetitem +// PYSIDE-2404: Usage of the `get()` function not necessary, the type exists. if (PyIndex_Check(_key)) { Py_ssize_t _i = PyNumber_AsSsize_t(_key, PyExc_IndexError); if (_i == -1 && PyErr_Occurred()) @@ -684,7 +690,8 @@ if (PyIndex_Check(_key)) { PyErr_SetString(PyExc_ValueError, "bytearray must be of size 1"); return -1; } - } else if (Py_TYPE(_value) == reinterpret_cast(SbkPySide6_QtCoreTypes[SBK_QBYTEARRAY_IDX])) { + } else if (Py_TYPE(_value) == reinterpret_cast( + SbkPySide6_QtCoreTypeStructs[SBK_QByteArray_IDX].type)) { if (PyObject_Length(_value) != 1) { PyErr_SetString(PyExc_ValueError, "QByteArray must be of size 1"); return -1; @@ -721,7 +728,7 @@ if (PySlice_GetIndicesEx(_key, %CPPSELF.size(), &start, &stop, &step, &sliceleng Py_ssize_t value_length = 0; if (_value != nullptr && _value != Py_None) { if (!(PyBytes_Check(_value) || PyByteArray_Check(_value) - || Py_TYPE(_value) == reinterpret_cast(SbkPySide6_QtCoreTypes[SBK_QBYTEARRAY_IDX]))) { + || Py_TYPE(_value) == SbkPySide6_QtCoreTypeStructs[SBK_QByteArray_IDX].type)) { PyErr_Format(PyExc_TypeError, "bytes, bytearray or QByteArray is required, not %.200s", Py_TYPE(_value)->tp_name); return -1; @@ -778,10 +785,10 @@ static int SbkQByteArray_getbufferproc(PyObject *obj, Py_buffer *view, int flags view->len = cppSelf->size(); view->readonly = 0; view->itemsize = 1; - view->format = const_cast("c"); + view->format = (flags & PyBUF_FORMAT) == PyBUF_FORMAT ? const_cast("B") : nullptr; view->ndim = 1; view->shape = (flags & PyBUF_ND) == PyBUF_ND ? &(view->len) : nullptr; - view->strides = &view->itemsize; + view->strides = (flags & PyBUF_STRIDES) == PyBUF_STRIDES ? &(view->itemsize) : nullptr; view->suboffsets = nullptr; view->internal = nullptr; @@ -1001,62 +1008,85 @@ auto *ptr = reinterpret_cast(Shiboken::Buffer::getPointer(%PYARG_1, &si %PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](%0); // @snippet qtranslator-load -// @snippet qtimer-singleshot-1 -// %FUNCTION_NAME() - disable generation of c++ function call -(void) %2; // remove warning about unused variable -Shiboken::AutoDecRef emptyTuple(PyTuple_New(0)); -auto *timerType = Shiboken::SbkType(); -auto *pyTimer = timerType->tp_new(Shiboken::SbkType(), emptyTuple, nullptr); -timerType->tp_init(pyTimer, emptyTuple, nullptr); - -auto timer = %CONVERTTOCPP[QTimer *](pyTimer); -Shiboken::AutoDecRef result( - PyObject_CallMethod(pyTimer, "connect", "OsOs", - pyTimer, - SIGNAL(timeout()), - %PYARG_2, - %3) -); -Shiboken::Object::releaseOwnership(reinterpret_cast(pyTimer)); -Py_XDECREF(pyTimer); -timer->setSingleShot(true); -timer->connect(timer, &QTimer::timeout, timer, &QObject::deleteLater); -timer->start(%1); -// @snippet qtimer-singleshot-1 - -// @snippet qtimer-singleshot-2 -// %FUNCTION_NAME() - disable generation of c++ function call +// @snippet qtimer-singleshot-functorclass +struct QSingleShotTimerFunctor : public Shiboken::PyObjectHolder +{ +public: + using Shiboken::PyObjectHolder::PyObjectHolder; + + void operator()(); +}; + +void QSingleShotTimerFunctor::operator()() +{ + Shiboken::GilState state; + Shiboken::AutoDecRef arglist(PyTuple_New(0)); + Shiboken::AutoDecRef ret(PyObject_CallObject(object(), arglist)); + if (Shiboken::Errors::occurred()) + Shiboken::Errors::storeErrorOrPrint(); + release(); // single shot +} +// @snippet qtimer-singleshot-functorclass + +// @snippet qtimer-singleshot-direct-mapping Shiboken::AutoDecRef emptyTuple(PyTuple_New(0)); -auto *timerType = Shiboken::SbkType(); -auto *pyTimer = timerType->tp_new(Shiboken::SbkType(), emptyTuple, nullptr); -timerType->tp_init(pyTimer, emptyTuple, nullptr); -QTimer * timer = %CONVERTTOCPP[QTimer *](pyTimer); -timer->setSingleShot(true); - -if (PyObject_TypeCheck(%2, PySideSignalInstance_TypeF())) { - PySideSignalInstance *signalInstance = reinterpret_cast(%2); - Shiboken::AutoDecRef signalSignature(Shiboken::String::fromFormat("2%s", PySide::Signal::getSignature(signalInstance))); - Shiboken::AutoDecRef result( - PyObject_CallMethod(pyTimer, "connect", "OsOO", - pyTimer, - SIGNAL(timeout()), - PySide::Signal::getObject(signalInstance), - signalSignature.object()) - ); +%CPPSELF.%FUNCTION_NAME(%1, %2, %3); +// @snippet qtimer-singleshot-direct-mapping + +// @snippet qtimer-singleshot-functor +auto msec = %1; +if (msec == 0) { + if (PyObject_TypeCheck(%2, PySideSignalInstance_TypeF())) { + auto *signal = %PYARG_2; + auto cppCallback = [signal]() + { + Shiboken::GilState state; + Shiboken::AutoDecRef ret(PyObject_CallMethod(signal, "emit", "()")); + Py_DECREF(signal); + }; + + Py_INCREF(signal); + %CPPSELF.%FUNCTION_NAME(msec, cppCallback); + } else { + %CPPSELF.%FUNCTION_NAME(msec, QSingleShotTimerFunctor(%PYARG_2)); + } } else { - Shiboken::AutoDecRef result( - PyObject_CallMethod(pyTimer, "connect", "OsO", - pyTimer, - SIGNAL(timeout()), - %PYARG_2) - ); -} + // %FUNCTION_NAME() - disable generation of c++ function call + Shiboken::AutoDecRef emptyTuple(PyTuple_New(0)); + auto *timerType = Shiboken::SbkType(); + auto newFunc = reinterpret_cast(PepType_GetSlot(timerType, Py_tp_new)); + auto initFunc = reinterpret_cast(PepType_GetSlot(timerType, Py_tp_init)); + auto *pyTimer = newFunc(Shiboken::SbkType(), emptyTuple, nullptr); + initFunc(pyTimer, emptyTuple, nullptr); -timer->connect(timer, &QTimer::timeout, timer, &QObject::deleteLater, Qt::DirectConnection); -Shiboken::Object::releaseOwnership(reinterpret_cast(pyTimer)); -Py_XDECREF(pyTimer); -timer->start(%1); -// @snippet qtimer-singleshot-2 + QTimer * timer = %CONVERTTOCPP[QTimer *](pyTimer); + timer->setSingleShot(true); + + if (PyObject_TypeCheck(%2, PySideSignalInstance_TypeF())) { + PySideSignalInstance *signalInstance = reinterpret_cast(%2); + Shiboken::AutoDecRef signalSignature(Shiboken::String::fromFormat("2%s", PySide::Signal::getSignature(signalInstance))); + Shiboken::AutoDecRef result( + PyObject_CallMethod(pyTimer, "connect", "OsOO", + pyTimer, + SIGNAL(timeout()), + PySide::Signal::getObject(signalInstance), + signalSignature.object()) + ); + } else { + Shiboken::AutoDecRef result( + PyObject_CallMethod(pyTimer, "connect", "OsO", + pyTimer, + SIGNAL(timeout()), + %PYARG_2) + ); + } + + timer->connect(timer, &QTimer::timeout, timer, &QObject::deleteLater, Qt::DirectConnection); + Shiboken::Object::releaseOwnership(reinterpret_cast(pyTimer)); + Py_XDECREF(pyTimer); + timer->start(msec); +} +// @snippet qtimer-singleshot-functor // @snippet qtimer-singleshot-functor-context auto msec = %1; @@ -1076,8 +1106,8 @@ if (msec == 0) { } else { Shiboken::AutoDecRef emptyTuple(PyTuple_New(0)); auto *timerType = Shiboken::SbkType(); - auto newFunc = timerType->tp_new; - auto initFunc = timerType->tp_init; + auto newFunc = reinterpret_cast(PepType_GetSlot(timerType, Py_tp_new)); + auto initFunc = reinterpret_cast(PepType_GetSlot(timerType, Py_tp_init)); auto *pyTimer = newFunc(Shiboken::SbkType(), emptyTuple, nullptr); initFunc(pyTimer, emptyTuple, nullptr); @@ -1483,15 +1513,23 @@ double in = %CONVERTTOCPP[double](%in); // @snippet conversion-sbkobject // a class supported by QVariant? const QMetaType metaType = QVariant_resolveMetaType(Py_TYPE(%in)); +bool ok = false; if (metaType.isValid()) { QVariant var(metaType); - Shiboken::Conversions::SpecificConverter converter(metaType.name()); - converter.toCpp(pyIn, var.data()); - %out = var; -} else { - // If the type was not encountered, return a default PyObjectWrapper - %out = QVariant::fromValue(PySide::PyObjectWrapper(%in)); + auto converterO = converterForQtType(metaType.name()); + ok = converterO.has_value(); + if (ok) { + converterO.value().toCpp(pyIn, var.data()); + %out = var; + } else { + qWarning("%s: Cannot find a converter for \"%s\".", + __FUNCTION__, metaType.name()); + } } + +// If the type was not encountered, return a default PyObjectWrapper +if (!ok) + %out = QVariant::fromValue(PySide::PyObjectWrapper(%in)); // @snippet conversion-sbkobject // @snippet conversion-pydict @@ -1589,6 +1627,28 @@ return PyLong_FromVoidPtr(reinterpret_cast(%in)); return PySide::qStringToPyUnicode(%in); // @snippet return-pyunicode +// @snippet return-pyunicode-from-qlatin1string +#ifdef Py_LIMITED_API +return PySide::qStringToPyUnicode(QString::fromLatin1(%in)); +#else +return PyUnicode_FromKindAndData(PyUnicode_1BYTE_KIND, %in.constData(), %in.size()); +#endif +// @snippet return-pyunicode-from-qlatin1string + +// @snippet qlatin1string-check +static bool qLatin1StringCheck(PyObject *o) +{ + return PyUnicode_CheckExact(o) != 0 + && _PepUnicode_KIND(o) == PepUnicode_1BYTE_KIND; +} +// @snippet qlatin1string-check + +// @snippet conversion-pystring-qlatin1string +const char *data = reinterpret_cast(_PepUnicode_DATA(%in)); +const Py_ssize_t len = PyUnicode_GetLength(%in); +%out = QLatin1String(data, len); +// @snippet conversion-pystring-qlatin1string + // @snippet return-pyunicode-from-qanystringview return PySide::qStringToPyUnicode(%in.toString()); // @snippet return-pyunicode-from-qanystringview @@ -1627,11 +1687,10 @@ default: break; } -Shiboken::Conversions::SpecificConverter converter(cppInRef.typeName()); -if (converter) { - void *ptr = cppInRef.data(); - return converter.toPython(ptr); -} +auto converterO = converterForQtType(cppInRef.typeName()); +if (converterO.has_value()) + return converterO.value().toPython(cppInRef.data()); + PyErr_Format(PyExc_RuntimeError, "Can't find converter for '%s'.", %in.typeName()); return 0; // @snippet return-qvariant @@ -1745,8 +1804,9 @@ if (dataChar == nullptr) { // @snippet qdatastream-read-bytes // @snippet qloggingcategory_to_cpp +// PYSIDE-2404: Usage of the `get()` function not necessary, the type exists. QLoggingCategory *category{nullptr}; - Shiboken::Conversions::pythonToCppPointer(SbkPySide6_QtCoreTypes[SBK_QLOGGINGCATEGORY_IDX], + Shiboken::Conversions::pythonToCppPointer(SbkPySide6_QtCoreTypeStructs[SBK_QLoggingCategory_IDX].type, pyArgs[0], &(category)); // @snippet qloggingcategory_to_cpp @@ -1952,7 +2012,7 @@ Py_INCREF(callable); Shiboken::AutoDecRef locale(PyImport_ImportModule("locale")); Shiboken::AutoDecRef getLocale(PyObject_GetAttrString(locale, "getlocale")); Shiboken::AutoDecRef systemLocale(PyObject_CallObject(getLocale, nullptr)); - Shiboken::AutoDecRef localeCode(PyTuple_GetItem(systemLocale, 0)); + PyObject* localeCode = PyTuple_GetItem(systemLocale, 0); %RETURN_TYPE %0; if (localeCode != Py_None) { QString localeCodeStr = PySide::pyStringToQString(localeCode); @@ -2030,3 +2090,61 @@ Py_BEGIN_ALLOW_THREADS Py_END_ALLOW_THREADS // @snippet qcoreapplication-requestpermission +// @snippet qlockfile-getlockinfo +qint64 pid{}; +QString hostname, appname; +%CPPSELF.%FUNCTION_NAME(&pid, &hostname, &appname); +%PYARG_0 = PyTuple_New(3); +PyTuple_SET_ITEM(%PYARG_0, 0, %CONVERTTOPYTHON[qint64](pid)); +PyTuple_SET_ITEM(%PYARG_0, 1, %CONVERTTOPYTHON[QString](hostname)); +PyTuple_SET_ITEM(%PYARG_0, 2, %CONVERTTOPYTHON[QString](appname)); +// @snippet qlockfile-getlockinfo + +// @snippet darwin_permission_plugin +#ifdef Q_OS_DARWIN +#include +// register the static plugin and setup its metadata +Q_IMPORT_PLUGIN(QDarwinCameraPermissionPlugin) +Q_IMPORT_PLUGIN(QDarwinMicrophonePermissionPlugin) +Q_IMPORT_PLUGIN(QDarwinBluetoothPermissionPlugin) +Q_IMPORT_PLUGIN(QDarwinContactsPermissionPlugin) +Q_IMPORT_PLUGIN(QDarwinCalendarPermissionPlugin) +#endif +// @snippet darwin_permission_plugin + +// @snippet qt-modifier +PyObject *_inputDict = PyDict_New(); +// Note: The builtins line is no longer needed since Python 3.10. Undocumented! +PyDict_SetItemString(_inputDict, "__builtins__", PyEval_GetBuiltins()); +PyDict_SetItemString(_inputDict, "QtCore", module); +PyDict_SetItemString(_inputDict, "Qt", reinterpret_cast(pyType)); +// Explicitly not dereferencing the result. +PyRun_String(R"PY(if True: + from enum import Flag + from textwrap import dedent + from warnings import warn + # QtCore and Qt come as globals. + + def func_or(self, other): + if isinstance(self, Flag) and isinstance(other, Flag): + # this is normal or-ing flags together + return Qt.KeyboardModifier(self.value | other.value) + return QtCore.QKeyCombination(self, other) + + def func_add(self, other): + warn(dedent(f""" + The "+" operator is deprecated in Qt For Python 6.0 . + Please use "|" instead."""), stacklevel=2) + return func_or(self, other) + + Qt.KeyboardModifier.__or__ = func_or + Qt.KeyboardModifier.__ror__ = func_or + Qt.Modifier.__or__ = func_or + Qt.Modifier.__ror__ = func_or + Qt.KeyboardModifier.__add__ = func_add + Qt.KeyboardModifier.__radd__ = func_add + Qt.Modifier.__add__ = func_add + Qt.Modifier.__radd__ = func_add + +)PY", Py_file_input, _inputDict, _inputDict); +// @snippet qt-modifier diff --git a/sources/pyside6/PySide6/glue/qtgraphs.cpp b/sources/pyside6/PySide6/glue/qtgraphs.cpp index cf1a10eb..b5a5db79 100644 --- a/sources/pyside6/PySide6/glue/qtgraphs.cpp +++ b/sources/pyside6/PySide6/glue/qtgraphs.cpp @@ -2,7 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only // @snippet graphs-qsurfacedataproxy-resetarraynp -auto *data = QtGraphsHelper::surfaceDataFromNp(%1, %2, %3, %4, %5); +auto data = QtGraphsHelper::surfaceDataFromNp(%1, %2, %3, %4, %5); // %CPPSELF.%FUNCTION_NAME %CPPSELF.resetArray(data); // @snippet graphs-qsurfacedataproxy-resetarraynp diff --git a/sources/pyside6/PySide6/glue/qtgui.cpp b/sources/pyside6/PySide6/glue/qtgui.cpp index e38684a7..5c860a2b 100644 --- a/sources/pyside6/PySide6/glue/qtgui.cpp +++ b/sources/pyside6/PySide6/glue/qtgui.cpp @@ -6,7 +6,9 @@ ********************************************************************/ // @snippet gui-declarations +QT_BEGIN_NAMESPACE void qt_set_sequence_auto_mnemonic(bool); +QT_END_NAMESPACE // @snippet gui-declarations // @snippet qaccessible-pysidefactory @@ -781,12 +783,45 @@ auto *cppResult = new QtGuiHelper::QOverrideCursorGuard(); Shiboken::Object::getOwnership(%PYARG_0); // Ensure the guard is removed // @snippet qguiapplication-setoverridecursor +// @snippet qguiapplication-nativeInterface +bool hasNativeApp = false; +#if QT_CONFIG(xcb) +if (auto *x11App = %CPPSELF.nativeInterface()) { + hasNativeApp = true; + %PYARG_0 = %CONVERTTOPYTHON[QNativeInterface::QX11Application*](x11App); +} +#endif +if (!hasNativeApp) { + Py_INCREF(Py_None); + %PYARG_0 = Py_None; +} +// @snippet qguiapplication-nativeInterface + // @snippet qscreen-grabWindow WId id = %1; %RETURN_TYPE retval = %CPPSELF.%FUNCTION_NAME(id, %2, %3, %4, %5); %PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](retval); // @snippet qscreen-grabWindow +// @snippet qscreen-nativeInterface +bool hasNativeScreen = false; +#ifdef Q_OS_WIN +if (auto *winScreen = %CPPSELF.nativeInterface()) { + hasNativeScreen = true; + %PYARG_0 = %CONVERTTOPYTHON[QNativeInterface::QWindowsScreen*](winScreen); +} +#endif +if (!hasNativeScreen) { + Py_INCREF(Py_None); + %PYARG_0 = Py_None; +} +// @snippet qscreen-nativeInterface + +// @snippet qx11application-resource-ptr + auto *resource = %CPPSELF.%FUNCTION_NAME(); +%PYARG_0 = PyLong_FromVoidPtr(resource); +// @snippet qx11application-resource-ptr + // @snippet qwindow-fromWinId WId id = %1; %RETURN_TYPE retval = %CPPSELF.%FUNCTION_NAME(id); @@ -854,15 +889,31 @@ else %PYARG_0 = %CONVERTTOPYTHON[int](cppResult); // @snippet qdrag-exec-arg2 +// @snippet qquaternion-getaxisandangle-vector3d-float +QVector3D outVec{}; +float angle{}; +%CPPSELF.%FUNCTION_NAME(&outVec, &angle); +%PYARG_0 = PyTuple_New(2); +PyTuple_SET_ITEM(%PYARG_0, 0, %CONVERTTOPYTHON[QVector3D](outVec)); +PyTuple_SET_ITEM(%PYARG_0, 1, %CONVERTTOPYTHON[float](angle)); +// @snippet qquaternion-getaxisandangle-vector3d-float + +// @snippet qquaternion-geteulerangles +float pitch{}, yaw{}, roll{}; +%CPPSELF.%FUNCTION_NAME(&pitch, &yaw, &roll); +%PYARG_0 = PyTuple_New(3); +PyTuple_SET_ITEM(%PYARG_0, 0, %CONVERTTOPYTHON[float](pitch)); +PyTuple_SET_ITEM(%PYARG_0, 1, %CONVERTTOPYTHON[float](yaw)); +PyTuple_SET_ITEM(%PYARG_0, 2, %CONVERTTOPYTHON[float](roll)); +// @snippet qquaternion-geteulerangles + // @snippet qregion-len return %CPPSELF.rectCount(); // @snippet qregion-len // @snippet qregion-getitem -if (_i < 0 || _i >= %CPPSELF.rectCount()) { - PyErr_SetString(PyExc_IndexError, "index out of bounds"); - return nullptr; -} +if (_i < 0 || _i >= %CPPSELF.rectCount()) + return PyErr_Format(PyExc_IndexError, "index out of bounds"); const QRect cppResult = *(%CPPSELF.cbegin() + _i); return %CONVERTTOPYTHON[QRect](cppResult); diff --git a/sources/pyside6/PySide6/glue/qtmultimedia.cpp b/sources/pyside6/PySide6/glue/qtmultimedia.cpp index 3d46619f..ac8434b9 100644 --- a/sources/pyside6/PySide6/glue/qtmultimedia.cpp +++ b/sources/pyside6/PySide6/glue/qtmultimedia.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only // @snippet qvideoframe-bits +#include "object.h" %BEGIN_ALLOW_THREADS %RETURN_TYPE %0 = %CPPSELF.%FUNCTION_NAME(%1); %END_ALLOW_THREADS @@ -20,3 +21,8 @@ const unsigned char *data = %CPPSELF.%FUNCTION_NAME(); const auto size = %CPPSELF.byteCount(); %PYARG_0 = Shiboken::Buffer::newObject(data, size); // @snippet qaudiobuffer-const-data + +// @snippet qtaudio-namespace-compatibility-alias +Py_INCREF(pyType); +PyModule_AddObject(module, "QtAudio", reinterpret_cast(pyType)); +// @snippet qtaudio-namespace-compatibility-alias diff --git a/sources/pyside6/PySide6/glue/qtnetwork.cpp b/sources/pyside6/PySide6/glue/qtnetwork.cpp index f00780b4..f635f467 100644 --- a/sources/pyside6/PySide6/glue/qtnetwork.cpp +++ b/sources/pyside6/PySide6/glue/qtnetwork.cpp @@ -15,20 +15,28 @@ PyTuple_SET_ITEM(%PYARG_0, 1, %CONVERTTOPYTHON[QHostAddress](ha)); PyTuple_SET_ITEM(%PYARG_0, 2, %CONVERTTOPYTHON[quint16](port)); // @snippet qudpsocket-readdatagram -// @snippet qhostinfo-lookuphost-callable -auto *callable = %PYARG_2; -auto cppCallback = [callable](const QHostInfo &hostInfo) +// @snippet qhostinfo-lookuphost-functor +struct QHostInfoFunctor : public Shiboken::PyObjectHolder +{ +public: + using Shiboken::PyObjectHolder::PyObjectHolder; + + void operator()(const QHostInfo &hostInfo); +}; + +void QHostInfoFunctor::operator()(const QHostInfo &hostInfo) { Shiboken::GilState state; Shiboken::AutoDecRef arglist(PyTuple_New(1)); auto *pyHostInfo = %CONVERTTOPYTHON[QHostInfo](hostInfo); PyTuple_SET_ITEM(arglist.object(), 0, pyHostInfo); - Shiboken::AutoDecRef ret(PyObject_CallObject(callable, arglist)); - Py_DECREF(callable); -}; + Shiboken::AutoDecRef ret(PyObject_CallObject(object(), arglist)); + release(); // single shot +} +// @snippet qhostinfo-lookuphost-functor -Py_INCREF(callable); -%CPPSELF.%FUNCTION_NAME(%1, cppCallback); +// @snippet qhostinfo-lookuphost-callable +%CPPSELF.%FUNCTION_NAME(%1, QHostInfoFunctor(%PYARG_2)); // @snippet qhostinfo-lookuphost-callable // @snippet qipv6address-len @@ -58,3 +66,65 @@ quint8 item = %CONVERTTOCPP[quint8](_value); %CPPSELF.c[_i] = item; return 0; // @snippet qipv6address-setitem + +// @snippet qrestaccessmanager-functor +class QRestFunctor +{ +public: + explicit QRestFunctor(PyObject *callable) noexcept : m_callable(callable) + { + Py_INCREF(callable); + } + + void operator()(QRestReply &restReply); + +private: + PyObject *m_callable; +}; + +void QRestFunctor::operator()(QRestReply &restReply) +{ + Q_ASSERT(m_callable); + Shiboken::GilState state; + Shiboken::AutoDecRef arglist(PyTuple_New(1)); + auto *restReplyPtr = &restReply; + auto *pyRestReply = %CONVERTTOPYTHON[QRestReply*](restReplyPtr); + PyTuple_SET_ITEM(arglist.object(), 0, pyRestReply); + Shiboken::AutoDecRef ret(PyObject_CallObject(m_callable, arglist)); + Py_DECREF(m_callable); + m_callable = nullptr; +} +// @snippet qrestaccessmanager-functor + +// @snippet qrestaccessmanager-callback +auto *networkReply = %CPPSELF.%FUNCTION_NAME(%1, %2, QRestFunctor(%PYARG_3)); +%PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](networkReply); +// @snippet qrestaccessmanager-callback + +// @snippet qrestaccessmanager-data-callback +auto *networkReply = %CPPSELF.%FUNCTION_NAME(%1, %2, %3, QRestFunctor(%PYARG_4)); +%PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](networkReply); +// @snippet qrestaccessmanager-data-callback + +// @snippet qrestaccessmanager-method-data-callback +auto *networkReply = %CPPSELF.%FUNCTION_NAME(%1, %2, %3, %4, QRestFunctor(%PYARG_5)); +%PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](networkReply); +// @snippet qrestaccessmanager-method-data-callback + +// @snippet qrestreply-readjson +QJsonParseError jsonParseError; +std::optional documentOptional = %CPPSELF.%FUNCTION_NAME(&jsonParseError); + +PyObject *pyDocument{}; +if (documentOptional.has_value()) { + const auto &document = documentOptional.value(); + pyDocument = %CONVERTTOPYTHON[QJsonDocument](document); +} else { + pyDocument = Py_None; + Py_INCREF(Py_None); +} + +%PYARG_0 = PyTuple_New(2); +PyTuple_SetItem(%PYARG_0, 0, pyDocument); +PyTuple_SetItem(%PYARG_0, 1, %CONVERTTOPYTHON[QJsonParseError](jsonParseError)); +// @snippet qrestreply-readjson diff --git a/sources/pyside6/PySide6/glue/qtnetworkauth.cpp b/sources/pyside6/PySide6/glue/qtnetworkauth.cpp index f390605d..7877a8dd 100644 --- a/sources/pyside6/PySide6/glue/qtnetworkauth.cpp +++ b/sources/pyside6/PySide6/glue/qtnetworkauth.cpp @@ -1,12 +1,21 @@ // Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -// @snippet qabstractoauth-setmodifyparametersfunction -auto callable = %PYARG_1; -auto callback = [callable](QAbstractOAuth::Stage stage, QMultiMap* dictPointer) -> void +// @snippet qabstractoauth-lookuphost-functor +struct QAbstractOAuthModifyFunctor : public Shiboken::PyObjectHolder +{ +public: + using Shiboken::PyObjectHolder::PyObjectHolder; + + void operator()(QAbstractOAuth::Stage stage, QMultiMap* dictPointer); +}; + +void QAbstractOAuthModifyFunctor::operator()(QAbstractOAuth::Stage stage, + QMultiMap* dictPointer) { + auto *callable = object(); if (!PyCallable_Check(callable)) { - qWarning("Argument 1 of %FUNCTION_NAME must be a callable."); + qWarning("Argument 1 of setModifyParametersFunction() must be a callable."); return; } Shiboken::GilState state; @@ -26,12 +35,10 @@ auto callback = [callable](QAbstractOAuth::Stage stage, QMultiMapreplace(cppKey, cppValue); } } +} +// @snippet qabstractoauth-lookuphost-functor - Py_DECREF(callable); - return; - -}; -Py_INCREF(callable); -%CPPSELF.%FUNCTION_NAME(callback); +// @snippet qabstractoauth-setmodifyparametersfunction +%CPPSELF.%FUNCTION_NAME(QAbstractOAuthModifyFunctor(%PYARG_1)); // @snippet qabstractoauth-setmodifyparametersfunction diff --git a/sources/pyside6/PySide6/glue/qtpositioning.cpp b/sources/pyside6/PySide6/glue/qtpositioning.cpp new file mode 100644 index 00000000..91c331c7 --- /dev/null +++ b/sources/pyside6/PySide6/glue/qtpositioning.cpp @@ -0,0 +1,14 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +/********************************************************************* + * INJECT CODE + ********************************************************************/ + +// @snippet darwin_location_permission_plugin +#ifdef Q_OS_DARWIN +#include +// register the static plugin and setup its metadata +Q_IMPORT_PLUGIN(QDarwinLocationPermissionPlugin) +#endif +// @snippet darwin_location_permission_plugin diff --git a/sources/pyside6/PySide6/glue/qtqml.cpp b/sources/pyside6/PySide6/glue/qtqml.cpp index 20bcfff4..a56db8de 100644 --- a/sources/pyside6/PySide6/glue/qtqml.cpp +++ b/sources/pyside6/PySide6/glue/qtqml.cpp @@ -62,3 +62,29 @@ return %CONVERTTOPYTHON[%RETURN_TYPE](retval); // @snippet qmlsingleton %PYARG_0 = PySide::Qml::qmlSingletonMacro(%ARGUMENT_NAMES); // @snippet qmlsingleton + +// @snippet qqmlengine-singletoninstance-qmltypeid +QJSValue instance = %CPPSELF.singletonInstance(%1); +if (instance.isNull()) { + Py_INCREF(Py_None); + %PYARG_0 = Py_None; +} else if (instance.isQObject()) { + QObject *result = instance.toQObject(); + %PYARG_0 = %CONVERTTOPYTHON[QObject *](result); +} else { + %PYARG_0 = %CONVERTTOPYTHON[QJSValue](instance); +} +// @snippet qqmlengine-singletoninstance-qmltypeid + +// @snippet qqmlengine-singletoninstance-typename +QJSValue instance = %CPPSELF.singletonInstance(%1, %2); +if (instance.isNull()) { + Py_INCREF(Py_None); + %PYARG_0 = Py_None; +} else if (instance.isQObject()) { + QObject *result = instance.toQObject(); + %PYARG_0 = %CONVERTTOPYTHON[QObject *](result); +} else { + %PYARG_0 = %CONVERTTOPYTHON[QJSValue](instance); +} +// @snippet qqmlengine-singletoninstance-typename diff --git a/sources/pyside6/PySide6/glue/qtquicktest.cpp b/sources/pyside6/PySide6/glue/qtquicktest.cpp new file mode 100644 index 00000000..f41735dd --- /dev/null +++ b/sources/pyside6/PySide6/glue/qtquicktest.cpp @@ -0,0 +1,50 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +/********************************************************************* + * INJECT CODE + ********************************************************************/ + +// @snippet call-quick-test-main +static int callQuickTestMain(const QString &name, QObject *setup, + QStringList argv, QString dir) +{ + if (dir.isEmpty()) + dir = QDir::currentPath(); + if (argv.isEmpty()) + argv.append(name); + + std::vector argvB; + std::vector argvC; + const auto argc = argv.size(); + argvB.reserve(argc); + argvC.reserve(argc); + for (const auto &arg : argv) { + argvB.emplace_back(arg.toUtf8()); + argvC.push_back(argvB.back().data()); + } + + return quick_test_main_with_setup(int(argc), argvC.data(), + name.toUtf8().constData(), + dir.toUtf8().constData(), setup); +} +// @snippet call-quick-test-main + +// @snippet quick-test-main +const int exitCode = callQuickTestMain(%1, nullptr, %2, %3); +%PYARG_0 = %CONVERTTOPYTHON[int](exitCode); +// @snippet quick-test-main + +// @snippet quick-test-main_with_setup +Shiboken::AutoDecRef pySetupObject(PyObject_CallObject(reinterpret_cast(%2), nullptr)); +if (pySetupObject.isNull() || PyErr_Occurred() != nullptr) + return nullptr; + +/// Convenience to convert a PyObject to QObject +QObject *setupObject = PySide::convertToQObject(pySetupObject.object(), true /* raiseError */); +if (setupObject == nullptr) + return nullptr; + +const int exitCode = callQuickTestMain(%1, setupObject, %3, %4); +%PYARG_0 = %CONVERTTOPYTHON[int](exitCode); +// @snippet quick-test-main_with_setup diff --git a/sources/pyside6/PySide6/glue/qtuitools.cpp b/sources/pyside6/PySide6/glue/qtuitools.cpp index c87cf9e7..1835ed09 100644 --- a/sources/pyside6/PySide6/glue/qtuitools.cpp +++ b/sources/pyside6/PySide6/glue/qtuitools.cpp @@ -45,7 +45,7 @@ static PyObject *QUiLoadedLoadUiFromDevice(QUiLoader *self, QIODevice *dev, QWid } if (!PyErr_Occurred()) - PyErr_SetString(PyExc_RuntimeError, "Unable to open/read ui device"); + PyErr_Format(PyExc_RuntimeError, "Unable to open/read ui device"); return nullptr; } @@ -87,15 +87,15 @@ char *arg1 = PyBytes_AsString(strObj); QByteArray uiFileName(arg1); Py_DECREF(strObj); -QFile uiFile(QString::fromUtf8(uiFileName)); - -if (!uiFile.exists()) { - qCritical().noquote() << "File" << uiFileName << "does not exists"; +if (uiFileName.isEmpty()) { + qCritical() << "Error converting the UI filename to QByteArray"; Py_RETURN_NONE; } -if (uiFileName.isEmpty()) { - qCritical() << "Error converting the UI filename to QByteArray"; +QFile uiFile(QString::fromUtf8(uiFileName)); + +if (!uiFile.exists()) { + qCritical().noquote() << "File" << uiFileName << "does not exist"; Py_RETURN_NONE; } @@ -107,18 +107,26 @@ QStringList uicArgs = {QString::fromUtf8(uiFileName)}; QProcess uicProcess; uicProcess.start(uicBin, uicArgs); -if (!uicProcess.waitForFinished()) { - qCritical() << "Cannot run 'pyside6-uic': " << uicProcess.errorString() << " - " - << "Exit status " << uicProcess.exitStatus() - << " (" << uicProcess.exitCode() << ")\n" - << "Check if 'pyside6-uic' is in PATH"; +if (!uicProcess.waitForStarted()) { + qCritical().noquote() << "Cannot run '" << uicBin << "': " + << uicProcess.errorString() << " - Check if 'pyside6-uic' is in PATH"; Py_RETURN_NONE; } + +if (!uicProcess.waitForFinished() + || uicProcess.exitStatus() != QProcess::NormalExit + || uicProcess.exitCode() != 0) { + qCritical().noquote() << '\'' << uicBin << "' failed: " + << uicProcess.errorString() << " - Exit status " << uicProcess.exitStatus() + << " (" << uicProcess.exitCode() << ")\n"; + Py_RETURN_NONE; +} + QByteArray uiFileContent = uicProcess.readAllStandardOutput(); QByteArray errorOutput = uicProcess.readAllStandardError(); if (!errorOutput.isEmpty()) { - qCritical().noquote() << errorOutput; + qCritical().noquote() << '\'' << uicBin << "' failed: " << errorOutput; Py_RETURN_NONE; } @@ -142,8 +150,8 @@ QXmlStreamReader reader(&uiFile); while (!reader.atEnd() && baseClassName.isEmpty() && className.isEmpty()) { auto token = reader.readNext(); if (token == QXmlStreamReader::StartElement && reader.name() == u"widget") { - baseClassName = reader.attributes().value(QLatin1String("class")).toUtf8(); - className = reader.attributes().value(QLatin1String("name")).toUtf8(); + baseClassName = reader.attributes().value(QLatin1StringView("class")).toUtf8(); + className = reader.attributes().value(QLatin1StringView("name")).toUtf8(); } } diff --git a/sources/pyside6/PySide6/glue/qtwebenginecore.cpp b/sources/pyside6/PySide6/glue/qtwebenginecore.cpp index 0a0a00ec..76a7c6d7 100644 --- a/sources/pyside6/PySide6/glue/qtwebenginecore.cpp +++ b/sources/pyside6/PySide6/glue/qtwebenginecore.cpp @@ -1,36 +1,64 @@ // Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -// @snippet qwebenginecookiestore-setcookiefilter -auto callable = %PYARG_1; -auto callback = [callable](const QWebEngineCookieStore::FilterRequest& filterRequest) -> bool +// @snippet qwebenginecookiestore-functor +struct QWebEngineCookieFilterFunctor : public Shiboken::PyObjectHolder +{ + using Shiboken::PyObjectHolder::PyObjectHolder; + + bool operator()(const QWebEngineCookieStore::FilterRequest& filterRequest) const; +}; + +bool QWebEngineCookieFilterFunctor::operator()(const QWebEngineCookieStore::FilterRequest & + filterRequest) const { Shiboken::GilState state; Shiboken::AutoDecRef arglist(PyTuple_New(1)); PyTuple_SET_ITEM(arglist, 0, - %CONVERTTOPYTHON[QWebEngineCookieStore::FilterRequest](filterRequest)); - Py_INCREF(callable); - Shiboken::AutoDecRef ret(PyObject_CallObject(callable, arglist)); - Py_DECREF(callable); + %CONVERTTOPYTHON[QWebEngineCookieStore::FilterRequest](filterRequest)); + Shiboken::AutoDecRef ret(PyObject_CallObject(object(), arglist)); return ret.object() == Py_True; -}; +} +// @snippet qwebenginecookiestore-functor -%CPPSELF.%FUNCTION_NAME(callback); +// @snippet qwebenginecookiestore-setcookiefilter +%CPPSELF.%FUNCTION_NAME(QWebEngineCookieFilterFunctor(%PYARG_1)); // @snippet qwebenginecookiestore-setcookiefilter -// @snippet qwebengineprofile-setnotificationpresenter -auto callable = %PYARG_1; -auto callback = [callable](std::unique_ptr webEngineNotification) -> void +// @snippet qwebengineprofile-functor +struct QWebEngineNotificationFunctor : public Shiboken::PyObjectHolder +{ + using Shiboken::PyObjectHolder::PyObjectHolder; + + void operator()(std::unique_ptr webEngineNotification); +}; + +void QWebEngineNotificationFunctor::operator() + (std::unique_ptr webEngineNotification) { Shiboken::GilState state; Shiboken::AutoDecRef arglist(PyTuple_New(1)); auto *notification = webEngineNotification.release(); PyTuple_SET_ITEM(arglist.object(), 0, %CONVERTTOPYTHON[QWebEngineNotification*](notification)); - Py_INCREF(callable); - Shiboken::AutoDecRef ret(PyObject_CallObject(callable, arglist)); - Py_DECREF(callable); + Shiboken::AutoDecRef ret(PyObject_CallObject(object(), arglist)); }; +// @snippet qwebengineprofile-functor -%CPPSELF.%FUNCTION_NAME(callback); // @snippet qwebengineprofile-setnotificationpresenter +%CPPSELF.%FUNCTION_NAME(QWebEngineNotificationFunctor(%PYARG_1)); +// @snippet qwebengineprofile-setnotificationpresenter + +// @snippet qwebenginepage-javascriptprompt-virtual-redirect +std::pair resultPair = javaScriptPromptPyOverride(gil, pyOverride.object(), securityOrigin, msg, defaultValue); +result->assign(resultPair.second); +return resultPair.first; +// @snippet qwebenginepage-javascriptprompt-virtual-redirect + +// @snippet qwebenginepage-javascriptprompt-return +QString str; +%RETURN_TYPE retval_ = %CPPSELF.%FUNCTION_NAME(%1, %2, %3, &str); +%PYARG_0 = PyTuple_New(2); +PyTuple_SET_ITEM(%PYARG_0, 0, %CONVERTTOPYTHON[%RETURN_TYPE](retval_)); +PyTuple_SET_ITEM(%PYARG_0, 1, %CONVERTTOPYTHON[QString](str)); +// @snippet qwebenginepage-javascriptprompt-return diff --git a/sources/pyside6/PySide6/glue/qtwidgets.cpp b/sources/pyside6/PySide6/glue/qtwidgets.cpp index 9eaf750d..f886106c 100644 --- a/sources/pyside6/PySide6/glue/qtwidgets.cpp +++ b/sources/pyside6/PySide6/glue/qtwidgets.cpp @@ -217,11 +217,21 @@ if (_widget) { #ifndef _QLAYOUT_HELP_FUNCTIONS_ #define _QLAYOUT_HELP_FUNCTIONS_ // Guard for jumbo builds +static const char msgInvalidParameterAdd[] = + "Invalid parameter None passed to addLayoutOwnership()."; +static const char msgInvalidParameterRemoval[] = + "Invalid parameter None passed to removeLayoutOwnership()."; + void addLayoutOwnership(QLayout *layout, QLayoutItem *item); void removeLayoutOwnership(QLayout *layout, QWidget *widget); inline void addLayoutOwnership(QLayout *layout, QWidget *widget) { + if (layout == nullptr || widget == nullptr) { + PyErr_SetString(PyExc_RuntimeError, msgInvalidParameterAdd); + return; + } + //transfer ownership to parent widget QWidget *lw = layout->parentWidget(); QWidget *pw = widget->parentWidget(); @@ -248,6 +258,11 @@ inline void addLayoutOwnership(QLayout *layout, QWidget *widget) inline void addLayoutOwnership(QLayout *layout, QLayout *other) { + if (layout == nullptr || other == nullptr) { + PyErr_SetString(PyExc_RuntimeError, msgInvalidParameterAdd); + return; + } + //transfer all children widgets from other to layout parent widget QWidget *parent = layout->parentWidget(); if (!parent) { @@ -274,8 +289,11 @@ inline void addLayoutOwnership(QLayout *layout, QLayout *other) inline void addLayoutOwnership(QLayout *layout, QLayoutItem *item) { - if (!item) + + if (layout == nullptr || item == nullptr) { + PyErr_SetString(PyExc_RuntimeError, msgInvalidParameterAdd); return; + } if (QWidget *w = item->widget()) { addLayoutOwnership(layout, w); @@ -291,6 +309,11 @@ inline void addLayoutOwnership(QLayout *layout, QLayoutItem *item) static void removeWidgetFromLayout(QLayout *layout, QWidget *widget) { + if (layout == nullptr || widget == nullptr) { + PyErr_SetString(PyExc_RuntimeError, msgInvalidParameterRemoval); + return; + } + if (QWidget *parent = widget->parentWidget()) { //give the ownership to parent Shiboken::AutoDecRef pyParent(%CONVERTTOPYTHON[QWidget *](parent)); @@ -308,9 +331,8 @@ static void removeWidgetFromLayout(QLayout *layout, QWidget *widget) inline void removeLayoutOwnership(QLayout *layout, QLayoutItem *item) { - - if (item == nullptr) { - PyErr_Format(PyExc_RuntimeError, "Item for removal from layout is None, or invalid."); + if (layout == nullptr || item == nullptr) { + PyErr_SetString(PyExc_RuntimeError, msgInvalidParameterRemoval); return; } @@ -329,8 +351,10 @@ inline void removeLayoutOwnership(QLayout *layout, QLayoutItem *item) inline void removeLayoutOwnership(QLayout *layout, QWidget *widget) { - if (!widget) + if (layout == nullptr || widget == nullptr) { + PyErr_SetString(PyExc_RuntimeError, msgInvalidParameterRemoval); return; + } for (int i = 0, i_max = layout->count(); i < i_max; ++i) { QLayoutItem *item = layout->itemAt(i); @@ -347,9 +371,10 @@ inline void removeLayoutOwnership(QLayout *layout, QWidget *widget) %CPPSELF.setAlignment(%1); // @snippet qlayout-setalignment -// @snippet addownership-0 -addLayoutOwnership(%CPPSELF, %0); -// @snippet addownership-0 +// @snippet addownership-item-at +if (%0 != nullptr) + addLayoutOwnership(%CPPSELF, %0); +// @snippet addownership-item-at // @snippet addownership-1 addLayoutOwnership(%CPPSELF, %1); @@ -522,15 +547,20 @@ Shiboken::Object::keepReference(reinterpret_cast(%PYSELF), "__style // @snippet qwidget-style QStyle *myStyle = %CPPSELF->style(); if (myStyle && qApp) { -%PYARG_0 = %CONVERTTOPYTHON[QStyle *](myStyle); + bool keepReference = true; + %PYARG_0 = %CONVERTTOPYTHON[QStyle *](myStyle); QStyle *appStyle = qApp->style(); if (appStyle == myStyle) { Shiboken::AutoDecRef pyApp(%CONVERTTOPYTHON[QApplication *](qApp)); - Shiboken::Object::setParent(pyApp, %PYARG_0); - Shiboken::Object::releaseOwnership(%PYARG_0); - } else { - Shiboken::Object::keepReference(reinterpret_cast(%PYSELF), "__style__", %PYARG_0); + // Do not set parentship when qApp is embedded + if (Shiboken::Object::wasCreatedByPython(reinterpret_cast(pyApp.object()))) { + Shiboken::Object::setParent(pyApp, %PYARG_0); + Shiboken::Object::releaseOwnership(%PYARG_0); + keepReference = false; + } } + if (keepReference) + Shiboken::Object::keepReference(reinterpret_cast(%PYSELF), "__style__", %PYARG_0); } // @snippet qwidget-style @@ -709,49 +739,49 @@ const char *styleOptionType(const QStyleOption *o) case QStyleOption::SO_Default: break; case QStyleOption::SO_FocusRect: - return "StyleOptionFocusRect"; + return "QStyleOptionFocusRect"; case QStyleOption::SO_Button: - return "StyleOptionButton"; + return "QStyleOptionButton"; case QStyleOption::SO_Tab: - return "StyleOptionTab"; + return "QStyleOptionTab"; case QStyleOption::SO_MenuItem: - return "StyleOptionMenuItem"; + return "QStyleOptionMenuItem"; case QStyleOption::SO_Frame: - return "StyleOptionFrame"; + return "QStyleOptionFrame"; case QStyleOption::SO_ProgressBar: - return "StyleOptionProgressBar"; + return "QStyleOptionProgressBar"; case QStyleOption::SO_ToolBox: - return "StyleOptionToolBox"; + return "QStyleOptionToolBox"; case QStyleOption::SO_Header: - return "StyleOptionHeader"; + return "QStyleOptionHeader"; case QStyleOption::SO_DockWidget: - return "StyleOptionDockWidget"; + return "QStyleOptionDockWidget"; case QStyleOption::SO_ViewItem: - return "StyleOptionViewItem"; + return "QStyleOptionViewItem"; case QStyleOption::SO_TabWidgetFrame: - return "StyleOptionTabWidgetFrame"; + return "QStyleOptionTabWidgetFrame"; case QStyleOption::SO_TabBarBase: - return "StyleOptionTabBarBase"; + return "QStyleOptionTabBarBase"; case QStyleOption::SO_RubberBand: - return "StyleOptionRubberBand"; + return "QStyleOptionRubberBand"; case QStyleOption::SO_ToolBar: - return "StyleOptionToolBar"; + return "QStyleOptionToolBar"; case QStyleOption::SO_GraphicsItem: - return "StyleOptionGraphicsItem"; + return "QStyleOptionGraphicsItem"; case QStyleOption::SO_Slider: - return "StyleOptionSlider"; + return "QStyleOptionSlider"; case QStyleOption::SO_SpinBox: - return "StyleOptionSpinBox"; + return "QStyleOptionSpinBox"; case QStyleOption::SO_ToolButton: - return "StyleOptionToolButton"; + return "QStyleOptionToolButton"; case QStyleOption::SO_ComboBox: - return "StyleOptionComboBox"; + return "QStyleOptionComboBox"; case QStyleOption::SO_TitleBar: - return "StyleOptionTitleBar"; + return "QStyleOptionTitleBar"; case QStyleOption::SO_GroupBox: - return "StyleOptionGroupBox"; + return "QStyleOptionGroupBox"; case QStyleOption::SO_SizeGrip: - return "StyleOptionSizeGrip"; + return "QStyleOptionSizeGrip"; default: break; } @@ -762,10 +792,8 @@ const char *styleOptionType(const QStyleOption *o) // @snippet qwizardpage-registerfield auto *signalInst = reinterpret_cast(%PYARG_4); const auto data = PySide::Signal::getEmitterData(signalInst); -if (data.methodIndex == -1) { - PyErr_SetString(PyExc_RuntimeError, "QWizardPage::registerField(): Unable to retrieve signal emitter."); - return nullptr; -} +if (data.methodIndex == -1) + return PyErr_Format(PyExc_RuntimeError, "QWizardPage::registerField(): Unable to retrieve signal emitter."); const auto method = data.emitter->metaObject()->method(data.methodIndex); const QByteArray signature = QByteArrayLiteral("2") + method.methodSignature(); %BEGIN_ALLOW_THREADS @@ -773,6 +801,14 @@ const QByteArray signature = QByteArrayLiteral("2") + method.methodSignature(); %END_ALLOW_THREADS // @snippet qwizardpage-registerfield +// The constructor heuristics generate setting a parent-child relationship +// when creating a QDialog with parent. This causes the dialog to leak +// when it synchronous exec() is used instead of asynchronous show(). +// In that case, remove the parent-child relationship. +// @snippet qdialog-exec-remove-parent-relation +Shiboken::Object::removeParent(reinterpret_cast(%PYSELF)); +// @snippet qdialog-exec-remove-parent-relation + /********************************************************************* * CONVERSIONS ********************************************************************/ diff --git a/sources/pyside6/PySide6/qiopipe.h b/sources/pyside6/PySide6/qiopipe.h new file mode 100644 index 00000000..6a325f18 --- /dev/null +++ b/sources/pyside6/PySide6/qiopipe.h @@ -0,0 +1,36 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QIOPIPE_H +#define QIOPIPE_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QIODevice; + +namespace QtCoreHelper +{ + +class QIOPipePrivate; +class QIOPipe : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QIOPipe) + +public: + QIOPipe(QObject *parent = nullptr); + + bool open(QIODeviceBase::OpenMode mode); + + QIODevice *end1() const; + QIODevice *end2() const; +}; + +} // namespace QtCoreHelper + +QT_END_NAMESPACE + +#endif // QIOPIPE_H diff --git a/sources/pyside6/PySide6/qpydesignerextensions.h b/sources/pyside6/PySide6/qpydesignerextensions.h index 04764c9a..871fa828 100644 --- a/sources/pyside6/PySide6/qpydesignerextensions.h +++ b/sources/pyside6/PySide6/qpydesignerextensions.h @@ -21,6 +21,10 @@ Q_DECLARE_INTERFACE(QDesignerTaskMenuExtension, "org.qt-project.Qt.Designer.Task Q_DECLARE_INTERFACE(QDesignerCustomWidgetCollectionInterface, "org.qt-project.Qt.QDesignerCustomWidgetCollectionInterface") #endif +struct _object; // PyObject + +QT_BEGIN_NAMESPACE + // Extension implementations need to inherit QObject which cannot be done in Python. // Provide a base class (cf QPyTextObject). @@ -56,8 +60,6 @@ public: explicit QPyDesignerTaskMenuExtension(QObject *parent = nullptr) : QObject(parent) {} }; -struct _object; // PyObject - class QPyDesignerCustomWidgetCollection : public QDesignerCustomWidgetCollectionInterface { public: @@ -77,4 +79,6 @@ private: QList m_customWidgets; }; +QT_END_NAMESPACE + #endif // QPYDESIGNEREXTENSIONS_H diff --git a/sources/pyside6/PySide6/qpyqmlparserstatus.h b/sources/pyside6/PySide6/qpyqmlparserstatus.h index 897ebfb5..24dcdda7 100644 --- a/sources/pyside6/PySide6/qpyqmlparserstatus.h +++ b/sources/pyside6/PySide6/qpyqmlparserstatus.h @@ -11,6 +11,8 @@ Q_DECLARE_INTERFACE(QQmlParserStatus, "org.qt-project.Qt.QQmlParserStatus") #endif +QT_BEGIN_NAMESPACE + // Inherit from QObject such that QQmlParserStatus can be found at // a fixed offset (RegisterType::parserStatusCast). class QPyQmlParserStatus : public QObject, public QQmlParserStatus @@ -21,4 +23,6 @@ public: explicit QPyQmlParserStatus(QObject *parent = nullptr) : QObject(parent) {} }; +QT_END_NAMESPACE + #endif // QPYQMLPARSERSTATUS_H diff --git a/sources/pyside6/PySide6/qpyqmlpropertyvaluesource.h b/sources/pyside6/PySide6/qpyqmlpropertyvaluesource.h index 8e8fc24c..0b6a0b95 100644 --- a/sources/pyside6/PySide6/qpyqmlpropertyvaluesource.h +++ b/sources/pyside6/PySide6/qpyqmlpropertyvaluesource.h @@ -10,6 +10,8 @@ Q_DECLARE_INTERFACE(QQmlPropertyValueSource, "org.qt-project.Qt.QQmlPropertyValueSource") #endif +QT_BEGIN_NAMESPACE + // Inherit from QObject such that QQmlPropertyValueSource can be found at // a fixed offset (RegisterType::valueSourceCast). class QPyQmlPropertyValueSource : public QObject, public QQmlPropertyValueSource @@ -20,4 +22,6 @@ public: explicit QPyQmlPropertyValueSource(QObject *parent = nullptr) : QObject(parent) {} }; +QT_END_NAMESPACE + #endif // QPYQMLPROPERTYVALUESOURCE_H diff --git a/sources/pyside6/PySide6/qpytextobject.h b/sources/pyside6/PySide6/qpytextobject.h index c667b298..45bb0bd5 100644 --- a/sources/pyside6/PySide6/qpytextobject.h +++ b/sources/pyside6/PySide6/qpytextobject.h @@ -13,6 +13,7 @@ Q_DECLARE_INTERFACE(QTextObjectInterface, "org.qt-project.Qt.QTextObjectInterface") #endif +QT_BEGIN_NAMESPACE class QPyTextObject : public QObject, public QTextObjectInterface { Q_OBJECT @@ -20,6 +21,8 @@ class QPyTextObject : public QObject, public QTextObjectInterface public: QPyTextObject(QObject *parent = nullptr) : QObject(parent) {} }; +QT_END_NAMESPACE + #endif diff --git a/sources/pyside6/PySide6/qtcorehelper.h b/sources/pyside6/PySide6/qtcorehelper.h index 3579d83c..589d0b29 100644 --- a/sources/pyside6/PySide6/qtcorehelper.h +++ b/sources/pyside6/PySide6/qtcorehelper.h @@ -9,6 +9,8 @@ #include +QT_BEGIN_NAMESPACE + namespace QtCoreHelper { using MutexLocker = QT_PREPEND_NAMESPACE(QMutexLocker); @@ -115,4 +117,6 @@ namespace QtCoreHelper { } // namespace QtCoreHelper +QT_END_NAMESPACE + #endif // QTCOREHELPER_H diff --git a/sources/pyside6/PySide6/qtdatavisualization_helper.h b/sources/pyside6/PySide6/qtdatavisualization_helper.h index 6884900e..8fee4492 100644 --- a/sources/pyside6/PySide6/qtdatavisualization_helper.h +++ b/sources/pyside6/PySide6/qtdatavisualization_helper.h @@ -6,8 +6,8 @@ #include -#include -#include +#include +#include namespace QtDataVisualizationHelper { diff --git a/sources/pyside6/PySide6/qtdbushelper.h b/sources/pyside6/PySide6/qtdbushelper.h index b59d2db9..7389ad81 100644 --- a/sources/pyside6/PySide6/qtdbushelper.h +++ b/sources/pyside6/PySide6/qtdbushelper.h @@ -8,6 +8,7 @@ #include #include +QT_BEGIN_NAMESPACE namespace QtDBusHelper { // A Python-bindings friendly, non-template QDBusReply @@ -56,4 +57,6 @@ inline QDBusReply::QDBusReply() = default; } // namespace QtDBusHelper +QT_END_NAMESPACE + #endif // QTDBUSHELPER_H diff --git a/sources/pyside6/PySide6/qtgraphs_helper.h b/sources/pyside6/PySide6/qtgraphs_helper.h index 91ef818d..e488fc7d 100644 --- a/sources/pyside6/PySide6/qtgraphs_helper.h +++ b/sources/pyside6/PySide6/qtgraphs_helper.h @@ -6,13 +6,13 @@ #include -#include -#include +#include +#include namespace QtGraphsHelper { -QSurfaceDataArray *surfaceDataFromNp(double x, double deltaX, double z, double deltaZ, - PyObject *data); +QSurfaceDataArray surfaceDataFromNp(double x, double deltaX, double z, double deltaZ, + PyObject *data); } // namespace QtGraphsHelper diff --git a/sources/pyside6/PySide6/qtguihelper.h b/sources/pyside6/PySide6/qtguihelper.h index dfb7b994..e537cfef 100644 --- a/sources/pyside6/PySide6/qtguihelper.h +++ b/sources/pyside6/PySide6/qtguihelper.h @@ -6,6 +6,7 @@ #include +QT_BEGIN_NAMESPACE namespace QtGuiHelper { class QOverrideCursorGuard @@ -29,5 +30,6 @@ namespace QtGuiHelper { }; } // namespace QtGuiHelper +QT_END_NAMESPACE #endif // QTGUIHELPER_H diff --git a/sources/pyside6/PySide6/support/deprecated.py b/sources/pyside6/PySide6/support/deprecated.py index f215f2ff..263dd3ed 100644 --- a/sources/pyside6/PySide6/support/deprecated.py +++ b/sources/pyside6/PySide6/support/deprecated.py @@ -13,59 +13,6 @@ Functions that are to be called for Note that this fixing code is run after all initializations, but before the import is finished. But that is no problem since the module is passed in. - -PYSIDE-1735: This is also used now for missing other functions (overwriting __or__ - in Qt.(Keyboard)Modifier). """ -import warnings -from textwrap import dedent - - -class PySideDeprecationWarningRemovedInQt6(Warning): - pass - - -def constData(self): - cls = self.__class__ - name = cls.__qualname__ - warnings.warn(dedent(f""" - {name}.constData is unpythonic and will be removed in Qt For Python 6.0 . - Please use {name}.data instead."""), PySideDeprecationWarningRemovedInQt6, stacklevel=2) - return cls.data(self) - - -# No longer needed but kept for reference. -def _unused_fix_for_QtGui(QtGui): - for name, cls in QtGui.__dict__.items(): - if name.startswith("QMatrix") and "data" in cls.__dict__: - cls.constData = constData - -# PYSIDE-1735: Fix for a special enum function -def fix_for_QtCore(QtCore): - from enum import Flag - Qt = QtCore.Qt - flag_or = Flag.__or__ - - def func_or(self, other): - if isinstance(self, Flag) and isinstance(other, Flag): - # this is normal or-ing flags together - return Qt.KeyboardModifier(self.value | other.value) - return QtCore.QKeyCombination(self, other) - - def func_add(self, other): - warnings.warn(dedent(f""" - The "+" operator is deprecated in Qt For Python 6.0 . - Please use "|" instead."""), PySideDeprecationWarningRemovedInQt6, stacklevel=2) - return func_or(self, other) - - Qt.KeyboardModifier.__or__ = func_or - Qt.KeyboardModifier.__ror__ = func_or - Qt.Modifier.__or__ = func_or - Qt.Modifier.__ror__ = func_or - Qt.KeyboardModifier.__add__ = func_add - Qt.KeyboardModifier.__radd__ = func_add - Qt.Modifier.__add__ = func_add - Qt.Modifier.__radd__ = func_add - # eof diff --git a/sources/pyside6/PySide6/support/generate_pyi.py b/sources/pyside6/PySide6/support/generate_pyi.py index 5a2cf05e..92808e2a 100644 --- a/sources/pyside6/PySide6/support/generate_pyi.py +++ b/sources/pyside6/PySide6/support/generate_pyi.py @@ -8,14 +8,14 @@ This script generates the .pyi files for all PySide modules. """ import argparse -import inspect +import inspect # noqa: F401 import logging import os import sys -import typing +import typing # noqa: F401 from pathlib import Path -from types import SimpleNamespace +from types import SimpleNamespace # noqa: F401 # Can we use forward references? USE_PEP563 = sys.version_info[:2] >= (3, 7) @@ -25,7 +25,7 @@ def generate_all_pyi(outpath, options): ps = os.pathsep if options.sys_path: # make sure to propagate the paths from sys_path to subprocesses - normpath = lambda x: os.fspath(Path(x).resolve()) + normpath = lambda x: os.fspath(Path(x).resolve()) # noqa: E731 sys_path = [normpath(_) for _ in options.sys_path] sys.path[0:0] = sys_path pypath = ps.join(sys_path) @@ -63,15 +63,16 @@ if __name__ == "__main__": parser = argparse.ArgumentParser( description="This script generates the .pyi file for all PySide modules.") parser.add_argument("modules", nargs="+", - help="'all' or the names of modules to build (QtCore QtGui etc.)") + help="'all' or the names of modules to build (QtCore QtGui etc.)") parser.add_argument("--quiet", action="store_true", help="Run quietly") - parser.add_argument("--check", action="store_true", help="Test the output if on Python 3") parser.add_argument("--outpath", - help="the output directory (default = binary location)") + help="the output directory (default = binary location)") parser.add_argument("--sys-path", nargs="+", - help="a list of strings prepended to sys.path") + help="a list of strings prepended to sys.path") parser.add_argument("--feature", nargs="+", choices=["snake_case", "true_property"], default=[], - help="""a list of feature names. Example: `--feature snake_case true_property`""") + help="""a list of feature names. """ + """Example: `--feature snake_case true_property`. """ + """Currently not available for PyPy.""") options = parser.parse_args() qtest_env = os.environ.get("QTEST_ENVIRONMENT", "") diff --git a/sources/pyside6/cmake/Macros/PySideModules.cmake b/sources/pyside6/cmake/Macros/PySideModules.cmake index bf6054a7..1e434f9f 100644 --- a/sources/pyside6/cmake/Macros/PySideModules.cmake +++ b/sources/pyside6/cmake/Macros/PySideModules.cmake @@ -21,6 +21,21 @@ macro(unmake_path varname) string(REPLACE "${PATH_SEP}" ";" ${varname} "${ARGN}") endmacro() +# Check for presence of QtOpenGL and modify module variables +# accordingly +macro(check_qt_opengl module include_var deps_var dropped_entries_var) + if (Qt${QT_MAJOR_VERSION}OpenGL_FOUND) + message(STATUS "Qt${QT_MAJOR_VERSION}${module}: Building with OpenGL") + list(APPEND ${include_var} ${Qt${QT_MAJOR_VERSION}OpenGL_INCLUDE_DIRS} + ${QtOpenGL_GEN_DIR}) + list(APPEND ${deps_var} QtOpenGL) + else() + message(STATUS "Qt${QT_MAJOR_VERSION}${module}: Dropping OpenGL") + # This is a dummy entry creating a conditional typesystem keyword + list(APPEND ${dropped_entries_var} "QtOpenGL") + endif() +endmacro() + # set size optimization flags for pyside6 macro(append_size_optimization_flags _module_name) if(NOT QFP_NO_OVERRIDE_OPTIMIZATION_FLAGS) diff --git a/sources/pyside6/cmake/PySideHelpers.cmake b/sources/pyside6/cmake/PySideHelpers.cmake index c728e7c4..23ceda6b 100644 --- a/sources/pyside6/cmake/PySideHelpers.cmake +++ b/sources/pyside6/cmake/PySideHelpers.cmake @@ -103,6 +103,7 @@ macro(collect_optional_modules) Quick Quick3D QuickControls2 + QuickTest QuickWidgets RemoteObjects Scxml diff --git a/sources/pyside6/doc/CMakeLists.txt b/sources/pyside6/doc/CMakeLists.txt index bf55263f..83583756 100644 --- a/sources/pyside6/doc/CMakeLists.txt +++ b/sources/pyside6/doc/CMakeLists.txt @@ -32,7 +32,7 @@ if(NOT CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) return() endif() else() - # We are building the docs as a standalone project, likely via setup.py build_rst_docs + # We are building the docs as a standalone project, likely via setup.py build_base_docs # command. Perform stricter sanity checks. if(NOT SPHINX_BUILD) message(FATAL_ERROR "sphinx-build command not found. Please set the SPHINX_BUILD variable.") @@ -51,6 +51,10 @@ else() endif() set(DOC_DATA_DIR "${CMAKE_CURRENT_BINARY_DIR}/qdoc-output") +# Directory for sphinx-generated files to build the HTML website. If changed, +# update "build_scripts/main.py" in "PysideBaseDocs" class at line: +# self.sphinx_src = self.out_dir / "base". +set(DOC_BASE_DIR "base") set(ENV_INHERITANCE_FILE "${CMAKE_CURRENT_BINARY_DIR}/inheritance.json") @@ -60,7 +64,7 @@ set(TS_ROOT "${ROOT}/PySide6") file(REMOVE ${CMAKE_CURRENT_LIST_DIR}/pyside.qdocconf ${CMAKE_CURRENT_LIST_DIR}/pyside.qdocconf.in) # We need to find the interpreter when running this only -# for a rst_build_docs case, and not a full doc build +# for the 'build_base_docs' case, and not a full doc build if (NOT FULLDOCSBUILD) find_package(Python COMPONENTS Interpreter) endif() @@ -82,7 +86,7 @@ set(TOOLS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../tools") if (FULLDOCSBUILD) # Fetch and transform the snippets from Qt set(SNIPPETS_TOOL "${TOOLS_DIR}/snippets_translate/main.py") - set(SNIPPETS_TARGET ${CMAKE_CURRENT_BINARY_DIR}/rst/codesnippets) + set(SNIPPETS_TARGET ${CMAKE_CURRENT_BINARY_DIR}/${DOC_BASE_DIR}/codesnippets) # Note QT_SRC_DIR points to 'qtbase', # so we use the general SRC directory to copy all the other snippets @@ -95,7 +99,7 @@ if (FULLDOCSBUILD) endif() # Generate example gallery -set(EXAMPLE_TOOL_TARGET "${CMAKE_CURRENT_BINARY_DIR}/rst/examples") +set(EXAMPLE_TOOL_TARGET "${CMAKE_CURRENT_BINARY_DIR}/${DOC_BASE_DIR}/examples") set(EXAMPLE_TOOL_OPTIONS --target "${EXAMPLE_TOOL_TARGET}" --qt-src-dir "${QT_SRC_DIR}") if (QUIET_BUILD) list(APPEND EXAMPLE_TOOL_OPTIONS "-q") @@ -190,7 +194,7 @@ endif() add_custom_target(apidoc COMMAND ${CMAKE_COMMAND} -E env INHERITANCE_FILE=${ENV_INHERITANCE_FILE} ${SHIBOKEN_PYTHON_INTERPRETER} ${SPHINX_BUILD} -b ${DOC_OUTPUT_FORMAT} - -j 6 ${CMAKE_CURRENT_BINARY_DIR}/rst html + -j 6 ${CMAKE_CURRENT_BINARY_DIR}/${DOC_BASE_DIR} html COMMENT "Generating PySide htmls..." ) @@ -224,14 +228,14 @@ else() endif() # create conf.py based on conf.py.in -configure_file("conf.py.in" "rst/conf.py" @ONLY) +configure_file("conf.py.in" "${DOC_BASE_DIR}/conf.py" @ONLY) -set(CODE_SNIPPET_ROOT "${CMAKE_CURRENT_BINARY_DIR}/rst/codesnippets") +set(CODE_SNIPPET_ROOT "${CMAKE_CURRENT_BINARY_DIR}/${DOC_BASE_DIR}/codesnippets") if (FULLDOCSBUILD) shiboken_get_tool_shell_wrapper(shiboken tool_wrapper) -add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/rst/PySide6/QtCore/index.rst" +add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${DOC_BASE_DIR}/PySide6/QtCore/index.rst" COMMAND ${tool_wrapper} $ @@ -243,26 +247,26 @@ add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/rst/PySide6/QtCore/index. --typesystem-paths="${QDOC_TYPESYSTEM_PATH}" --library-source-dir=${QT_SRC_DIR} --documentation-data-dir=${DOC_DATA_DIR}/webxml - --output-directory=${CMAKE_CURRENT_BINARY_DIR}/rst + --output-directory=${CMAKE_CURRENT_BINARY_DIR}/${DOC_BASE_DIR} --documentation-code-snippets-dir=${CODE_SNIPPET_ROOT} --snippets-path-rewrite=${QT_ROOT_PATH}:${CODE_SNIPPET_ROOT} - --documentation-extra-sections-dir=${CMAKE_CURRENT_BINARY_DIR}/rst/extras - --additional-documentation=${CMAKE_CURRENT_BINARY_DIR}/rst/additionaldocs.lst + --documentation-extra-sections-dir=${CMAKE_CURRENT_BINARY_DIR}/${DOC_BASE_DIR}/extras + --additional-documentation=${CMAKE_CURRENT_BINARY_DIR}/${DOC_BASE_DIR}/additionaldocs.lst --inheritance-file=${ENV_INHERITANCE_FILE} ${global_typesystem} WORKING_DIRECTORY ${${module}_SOURCE_DIR} COMMENT "Running generator to generate documentation...") endif() -add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/rst/extras" - COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}/rst +add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${DOC_BASE_DIR}/extras" + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}/${DOC_BASE_DIR} COMMENT "Copying docs...") add_custom_target("doc_copy" - DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/rst/extras") + DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/${DOC_BASE_DIR}/extras") add_custom_target("docrsts" - DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/rst/PySide6/QtCore/index.rst") + DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/${DOC_BASE_DIR}/PySide6/QtCore/index.rst") set(LIBEXEC_PATH "${QT6_INSTALL_PREFIX}/${QT6_INSTALL_LIBEXECS}") add_custom_target("licensedocrsts" @@ -270,7 +274,7 @@ add_custom_target("licensedocrsts" ${CMAKE_CURRENT_LIST_DIR}/qtattributionsscannertorst.py -l "${LIBEXEC_PATH}" ${CMAKE_CURRENT_LIST_DIR}/../../.. - ${CMAKE_CURRENT_BINARY_DIR}/rst/licenses.rst + ${CMAKE_CURRENT_BINARY_DIR}/${DOC_BASE_DIR}/licenses.rst COMMENT "Creating 3rdparty license documentation..." ) diff --git a/sources/pyside6/doc/PySide6/QtAsyncio/index.rst b/sources/pyside6/doc/PySide6/QtAsyncio/index.rst index 90c0ee67..326f6efc 100644 --- a/sources/pyside6/doc/PySide6/QtAsyncio/index.rst +++ b/sources/pyside6/doc/PySide6/QtAsyncio/index.rst @@ -131,6 +131,11 @@ coroutine has finished, while ``quit_qapp`` determines if the QCoreApplication should be shut down after asyncio has finished. It is possible for asyncio to finish while the QCoreApplication is kept alive. +An argument ``handle_sigint`` determines whether QtAsyncio should handle +SIGINT (Ctrl+C) and shut down the event loop when it is received. The +default is ``False``. Set this to ``True`` if you want QtAsyncio to take +care of handling SIGINT instead of your program. + Coroutines explained ^^^^^^^^^^^^^^^^^^^^ diff --git a/sources/pyside6/doc/_static/css/qt_style.css b/sources/pyside6/doc/_static/css/qt_style.css index 3e98c8ee..08c4646c 100644 --- a/sources/pyside6/doc/_static/css/qt_style.css +++ b/sources/pyside6/doc/_static/css/qt_style.css @@ -82,3 +82,19 @@ code.download:hover { text-decoration: none; padding: .375rem .75rem; } + +dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple):first-child > dt { + font-size: +2.25rem; + font-weight: 700; + color: #ff00dd; +} + +.theme-toggle svg{ + width: +1.25rem; + height: +2.25rem; +} + +.sd-card-title code span { + font-size: +1rem; + color: var(--color-brand-primary); +} diff --git a/sources/pyside6/doc/_tags/android.rst b/sources/pyside6/doc/_tags/android.rst new file mode 100644 index 00000000..08a30fc0 --- /dev/null +++ b/sources/pyside6/doc/_tags/android.rst @@ -0,0 +1,21 @@ +My tags: Android +################ + +.. toctree:: + :maxdepth: 1 + :caption: With this tag + + ../examples/example_bluetooth_heartrate_game.rst + ../examples/example_bluetooth_lowenergyscanner.rst + ../examples/example_location_mapviewer.rst + ../examples/example_multimedia_audiooutput.rst + ../examples/example_multimedia_audiosource.rst + ../examples/example_multimedia_camera.rst + ../examples/example_qml_editingmodel.rst + ../examples/example_qml_usingmodel.rst + ../examples/example_quick_models_objectlistmodel.rst + ../examples/example_quick_models_stringlistmodel.rst + ../examples/example_quick_painteditem.rst + ../examples/example_quickcontrols_contactslist.rst + ../examples/example_quickcontrols_gallery.rst + ../examples/example_widgets_widgets_digitalclock.rst diff --git a/sources/pyside6/doc/_tags/tagsindex.rst b/sources/pyside6/doc/_tags/tagsindex.rst new file mode 100644 index 00000000..be29337e --- /dev/null +++ b/sources/pyside6/doc/_tags/tagsindex.rst @@ -0,0 +1,12 @@ +:orphan: + +.. _tagoverview: + +Tags overview +############# + +.. toctree:: + :caption: Tags + :maxdepth: 1 + + Android (14) diff --git a/sources/pyside6/doc/commercial/index.rst b/sources/pyside6/doc/commercial/index.rst index 33c88cfe..e74419d6 100644 --- a/sources/pyside6/doc/commercial/index.rst +++ b/sources/pyside6/doc/commercial/index.rst @@ -109,7 +109,7 @@ simplify the installation step:: Complementary to the wheels, you will be able to download the sources as well. -.. note:: Wheels installed this way will be detectable by `Qt Creator`_, which +.. note:: Wheels installed this way will be detectable by `*Qt Creator*`_, which will offer you to install them for your current Python interpreter. Using account.qt.io @@ -180,11 +180,11 @@ the wheels are:: Qt Creator Integration ---------------------- -Qt Creator offers the option to create new |project| projects from the main +*Qt Creator* offers the option to create new |project| projects from the main wizard. To execute the projects, make sure that the proper *Python Interpreter* is -selected, so Qt Creator can use the commercial modules you just installed. +selected, so *Qt Creator* can use the commercial modules you just installed. Go to *Edit -> Preferences* where you can find the *Python* option that will show the following: @@ -197,7 +197,7 @@ select the main Python executable from your environment. This can be found on ``path_to_your_env/bin/python`` (macOS and Linux), or ``path_to_your_env\python.exe`` (Windows). -As an alternative, you can launch Qt Creator from within the virtual +As an alternative, you can launch *Qt Creator* from within the virtual environment, detecting your installation automatically. Migrating from other versions diff --git a/sources/pyside6/doc/conf.py.in b/sources/pyside6/doc/conf.py.in index 45defe9e..e195d227 100644 --- a/sources/pyside6/doc/conf.py.in +++ b/sources/pyside6/doc/conf.py.in @@ -30,7 +30,8 @@ extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.ifconfig', 'sphinx.ext.coverage', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.graphviz', 'inheritance_diagram', 'pysideinclude', 'sphinx.ext.viewcode', - 'sphinx_design', 'sphinx_copybutton', 'myst_parser', 'sphinx_tags',] + 'sphinx_design', 'sphinx_copybutton', 'myst_parser', 'sphinx_tags', + 'sphinx_toolbox.decorators'] myst_enable_extensions = [ "amsmath", @@ -227,7 +228,7 @@ intersphinx_mapping = {'shiboken6': ('shiboken6','@SHIBOKEN_INTERSPHINX_FILE@')} add_module_names = False # Skip some warnings when building the documentation with -# 'build_rst_docs' due to the lack of qdoc generated files, in charge +# 'build_base_docs' due to the lack of qdoc generated files, in charge # of sphinx modules (autodoc) and references. if @SKIP_SPHINX_WARNINGS@: suppress_warnings = ["autodoc", "autodoc.import_object", "ref.ref"] diff --git a/sources/pyside6/doc/considerations.rst b/sources/pyside6/doc/considerations.rst index 78bd85bd..dda1d8b0 100644 --- a/sources/pyside6/doc/considerations.rst +++ b/sources/pyside6/doc/considerations.rst @@ -383,3 +383,59 @@ cannot be written in the shortcut form because there is no surrounding PySide class that provides the forgiving mode implementation. Typically, the needed changes are easily found because they often occur in an import statement. + +Permission API +-------------- + +The cross-platform permission APIs were introduced to Qt in version 6.5 which are currently relevant +to platforms macOS, iOS, Android and WebAssembly. With this API, your Qt application can check and +request permission for certain features like Camera, Microphone, Location, Bluetooth, Contacts, +Calendar. More about permission API can be read in this `Blog post`_. + +When a PySide6 application that uses the permission API is run in interpreted mode, i.e., +``python .py``, the code implementing the permission API *will not work*. The only way +to make your PySide6 application using permission API work is to bundle the application. For Android, +this means using the `pyside6-android-deploy`_ tool and for macOS, this means using the +`pyside6-deploy`_ tool. + +When running in interpreted mode, you can skip over the permission check/request using the following +*if* condition + +:: + + is_deployed = "__compiled__" in globals() + if not is_deployed and sys.platform == "darwin": + # code implementing permission check and request + +This can also be seen in the PySide6 `Camera example`_. * __compiled__ * is a Nuitka attribute to +check if the application is run as a standalone application or run in interpreted mode with Python. + +Android +~~~~~~~~ + +For Android, `pyside6-android-deploy`_ takes care of identifying the necessary permissions needed by +the application and adding those permissions to the *AndroidManifest.xml* using the +** element. + +macOS +~~~~~ + +Since the Android platform does not automatically come bundled with a Python interpreter, it is +evident that to make a PySide6 application run on Android you have to package the PySide6 +application. This is not the case for desktop platforms like macOS where a Python interpreter and +its packages can be installed and run quite easily. + +The problem for macOS is that for the permission API to work you need a macOS bundle with an +*Info.plist* file that lists all the permissions required using the *usage description* string for +each permission used. When Python is run in interpreted mode, i.e., when you run Python, the Qt +permission API fetches the *Info.plist* from the Python interpreter by default which does not +contain the *usage description* strings for the permissions required. You can certainly modify the +*Info.plist* of the Python framework installation to make the Qt permission API work when running +a PySide6 application from the terminal. However, this is not recommended. Therefore, the only +viable solution is to bundle the PySide6 application as a macOS application bundle using +`pyside6-deploy`_. This macOS application bundle will have its own Info.plist file. + +.. _`Blog post`: https://www.qt.io/blog/permission-apis-in-qt-6.5 +.. _`Camera Example`: https://doc.qt.io/qtforpython-6/examples/example_multimedia_camera.html#camera-example +.. _`pyside6-android-deploy`: https://doc.qt.io/qtforpython-6/gettingstarted/package_details.html#deployment +.. _`pyside6-deploy`: https://doc.qt.io/qtforpython-6/gettingstarted/package_details.html#deployment diff --git a/sources/pyside6/doc/contents.rst b/sources/pyside6/doc/contents.rst index 04203a53..4b6c7ffa 100644 --- a/sources/pyside6/doc/contents.rst +++ b/sources/pyside6/doc/contents.rst @@ -7,6 +7,7 @@ commercial/index.rst gettingstarted/index.rst api.rst + tools/index.rst tutorials/index.rst examples/index.rst videos.rst diff --git a/sources/pyside6/doc/deployment/deployment-pyside6-android-deploy.rst b/sources/pyside6/doc/deployment/deployment-pyside6-android-deploy.rst new file mode 100644 index 00000000..53944f6e --- /dev/null +++ b/sources/pyside6/doc/deployment/deployment-pyside6-android-deploy.rst @@ -0,0 +1,211 @@ +.. _pyside6-android-deploy: + +pyside6-android-deploy: the Android deployment tool for Qt for Python +##################################################################### + +``pyside6-android-deploy`` is an easy-to-use tool for deploying PySide6 applications to different +Android architectures, namely *arm64-v8a, x86_64, x86 and armeabi-v7a*. This tool works similarly to +the ``pyside6-deploy`` tool and uses the same configuration file ``pysidedeploy.spec`` as +``pyside6-deploy`` to configure the deployment process. Using the deployment configuration +options either from the command line or from ``pysidedeploy.spec``, ``pyside6-android-deploy`` +configures the deployment to be initiated and invokes `buildozer`_, a tool used for packaging Python +applications to Android. + +The final output is a `.apk` or a `.aab` file created within the project's source directory. The +`mode` option specified under the :ref:`buildozer ` key in ``pysidedeploy.spec`` +determines whether a `.apk` or a `.aab` is created. + +.. warning:: Currently, users are required to cross-compile Qt for Python to generate the wheels + required for a specific Android target architecture. This requirement will disappear when + there are official Qt for Python Android wheels (*in progress*). Because of this + requirement ``pyside6-android-deploy`` will be considered in **Technical Preview**. + Instructions on cross-compiling Qt for Python for Android can be found + :ref:`here `. + +.. note:: ``pyside6-android-deploy`` only works on a Linux host at the moment. This constraint + is also because Qt for Python cross-compilation for Android currently only works on Linux + systems. + +How to use it? +============== + +Like ``pyside6-deploy``, there are :ref:`two different ways ` with which +you can deploy your PySide6 application using ``pyside6-android-deploy``. The only difference is +that for ``pyside6-android-deploy`` to work, the main Python entry point file should be named +``main.py``. + +.. _pysideandroiddeploy: + +pysidedeploy.spec +================= + +Like ``pyside6-deploy``, you can use the ``pysidedeploy.spec`` file to control the various +parameters of the deployment process. The file has multiple sections, with each section containing +multiple keys (parameters being controlled) assigned to a value. The advantages of such a file are +mentioned :ref:`here `. The benefit of using the same +``pysidedeploy.spec`` for both ``pyside6-deploy`` and ``pyside6-android-deploy`` is that you can +have one single file to control deployment to all platforms. + +The relevant parameters for ``pyside6-android-deploy`` are: + +**app** + * ``title``: The name of the application. + * ``project_dir``: Project directory. The general assumption made is that the project directory + is the parent directory of the main Python entry point file. + * ``input_file``: Path to the main Python entry point file. For ``pyside6-android-deploy`` this + file should be named `main.py`. + * ``project_file``: If it exists, this points to the path to the `Qt Creator Python Project File + .pyproject `_ file. Such a file in the project directory ensures + that deployment does not consider unnecessary files when bundling the executable. + * ``exec_directory``: The directory where the final executable is generated. + +**python** + * ``python_path``: Path to the Python executable. It is recommended to run + ``pyside6-android-deploy`` from a virtual environment as certain Python packages will be + installed onto the Python environment. However, note to keep the created virtual environment + outside the project directory so that ``pyside6-android-deploy`` does not try to package it + as well. + * ``android_packages``: The Python packages installed into the Python environment for deployment + to work. By default, the Python packages `buildozer`_ and `cpython`_ are installed. + +.. _qt_key: + +**qt** + * ``modules``: Comma-separated list of all the Qt modules used by the application. Just like the + other configuration options in ``pysidedeploy.spec``, this option is also computed automatically + by ``pyside6-android-deploy``. However, if you want to explicitly include certain Qt modules, + the module names can be appended to this list without the `Qt` prefix. + e.g. Network instead of QtNetwork + * ``plugins``: This field is *not relevant* for ``pyside6-android-deploy`` and is only specific to + ``pyside6-deploy``. The plugins relevant for ``pyside6-android-deploy`` are specified through + the ``plugins`` option under the :ref:`android ` key. + +.. _android_key: + +**android** + * ``wheel_pyside``: Specifies the path to the PySide6 Android wheel for a specific target + architecture. + * ``wheel_pyside``: Specifies the path to the Shiboken6 Android wheel for a specific target + architecture. + * ``plugins``: Comma-separated list of all the Qt plugins used by the application. Just like the + other configuration options in ``pysidedeploy.spec``, this option is also computed automatically + by ``pyside6-android-deploy``. However, if you want to to explicitly include certain Qt plugins, + the plugin names can be appended to this list. To see all the plugins bundled with PySide6, see + the `plugins` folder in the ``site-packages`` on your Python where PySide6 is installed. The + plugin name corresponds to their folder name. This field can be confused with the ``plugins`` + option under :ref:`qt ` key. In the future, they will be merged into one single option. + +.. _buildozer_key: + +**buildozer** + * ``mode``: Specifies one of the two modes - `release` and `debug`, to run `buildozer`_. The + `release` mode creates an *aab* while the `debug` mode creates an apk. The default mode is + `debug`. + * ``recipe_dir``: Specifies the path to the directory containing `python-for-android`_ recipes. + This option is automatically computed by ``pyside6-android-deploy`` during deployment. Without + the :ref:`--keep-deployment-files ` option of ``pyside6-android-deploy``, + the `recipe_dir` will point to a temporary directory that is deleted after the final Android + application package is created. + * ``jars_dir``: Specifies the path to the Qt Android `.jar` files that are relevant for + creating the Android application package. This option is automatically computed by + ``pyside6-android-deploy`` during deployment. Just like ``recipe_dir``, this field is also + *not relevant* unless used with the :ref:`--keep-deployment-files ` + option of ``pyside6-android-deploy``. + * ``ndk_path``: Specifies the path to the Android NDK used for packaging the application. + * ``sdk_path``: Specifies the path to the Android SDK used for packaging the application. + * ``local_libs``: Specifies non-Qt plugins or other libraries compatible with the Android target + to be loaded by the Android runtime on startup. + * ``sdk_path``: Specifies the path to the Android SDK used for packaging the application. + * ``arch``: Specifies the target architecture's instruction set. This option take one of the four + values - *aarch64, armv7a, i686, x86_64*. + +Command Line Options +==================== + +Here are all the command line options of ``pyside6-android-deploy``: + +* **-c/--config-file**: This option is used to specify the path to ``pysidedeploy.spec`` explicitly. + +* **--init**: Used to only create the ``pysidedeploy.spec`` file. + Usage:: + + pyside6-android-deploy --init + +* **-v/--verbose**: Runs ``pyside6-android-deploy`` in verbose mode. + +* **--dry-run**: Displays the commands being run to produce the Android application package. + +.. _keep_deployment_files: + +* **--keep-deployment-files**: When this option is added, it retains the build folders created by + `buildozer`_ during the deployment process. This includes the folder storing the + `python-for-android`_ recipes, relevant `.jar` files and even the Android Gradle project for the + application. + +* **-f/--force**: When this option is used, it assumes ``yes`` to all prompts and runs + ``pyside6-android-deploy`` non-interactively. ``pyside6-android-deploy`` prompts the user to + create a Python virtual environment, if not already in one. With this option, the current Python + environment is used irrespective of whether the current Python environment is a virtual + environment or not. + +* **--name**: Application name. + +* **--wheel-pyside**: Path to the PySide6 Android wheel for a specific target architecture. + +* **--wheel-shiboken**: Path to the Shiboken6 Android wheel for a specific target architecture. + +* **--ndk-path**: Path to the Android NDK used for packaging the application. + +* **--sdk-path**: Path to the Android SDK used for packaging the application. + +* **--extra-ignore-dirs**: Comma-separated directory names inside the project directory. These + directories will be skipped when searching for Python files relevant to the project. + +* **--extra-modules**: Comma-separated list of Qt modules to be added to the application, + in case they are not found automatically. The module name can either be specified + by omitting the prefix of Qt or including it eg: both Network and QtNetwork works. + +.. _cross_compile_android: + +Cross-compile Qt for Python wheels for Android +============================================== + +The cross-compilation of Qt for Python wheel for a specific Android target architecture needs to be +done only once per Qt version, irrespective of the number of applications you are deploying. +Currently, cross-compiling Qt for Python wheels only works with a Linux host. Follow these steps +to cross-compile Qt for Python Android wheels. + +#. `Download `_ and install Qt version for which you would like to create Qt for Python + wheels. + +#. Cloning the Qt for Python repository:: + + git clone https://code.qt.io/pyside/pyside-setup + +#. Check out the version that you want to build, for example 6.7. The version checked out has + to correspond to the Qt version downloaded in Step 1:: + + cd pyside-setup && git checkout 6.7 + +#. Installing the dependencies:: + + pip install -r requirements.txt + pip install -r tools/cross_compile_android/requirements.txt + +#. Run the cross-compilation Python script.:: + + python tools/cross_compile_android/main.py --plat-name=aarch64 --qt-install-path=/opt/Qt/6.7.0 + --auto-accept-license --skip-update + + *--qt-install-path* refers to the path where Qt 6.7.0 is installed. *--auto-accept-license* and + *--skip-update* are required for downloading and installing Android NDK and SDK if not already + specified through command line options or if they don't already exist in the + ``pyside6-android-deploy`` cache. Use --help to see all the other available options:: + + python tools/cross_compile_android/main.py --help + +.. _`buildozer`: https://buildozer.readthedocs.io/en/latest/ +.. _`python-for-android`: https://python-for-android.readthedocs.io/en/latest/ +.. _`qt_download`: https://www.qt.io/download +.. _`cpython`: https://pypi.org/project/Cython/ diff --git a/sources/pyside6/doc/deployment/deployment-pyside6-deploy.rst b/sources/pyside6/doc/deployment/deployment-pyside6-deploy.rst index f288dd32..6a218fb3 100644 --- a/sources/pyside6/doc/deployment/deployment-pyside6-deploy.rst +++ b/sources/pyside6/doc/deployment/deployment-pyside6-deploy.rst @@ -7,8 +7,17 @@ pyside6-deploy: the deployment tool for Qt for Python platforms. It is a wrapper around `Nuitka `_, a Python compiler that compiles your Python code to C code, and links with libpython to produce the final executable. -The final executable produced has a ``.exe`` suffix on Windows. For Linux and macOS, they have a -``.bin`` suffix. +The final executable produced has a ``.exe`` suffix on Windows, ``.bin`` on Linux and ``.app`` on +macOS. + +.. note:: Although using a virtual environment for Python is recommended for ``pyside6-deploy``, do + not add the virtual environment to the application directory you are trying to deploy. + ``pyside6-deploy`` will try to package this venv folder and will eventually fail. + +.. note:: The default version of Nuitka used with the tool is version ``2.3.2``. This can be + updated to a newer version by updating your ``pysidedeploy.spec`` file. + +.. _how_pysidedeploy: How to use it? ============== @@ -62,8 +71,10 @@ parameters of the deployment process. The file has multiple sections, with each multiple keys (parameters being controlled) assigned to a value. The advantages of such a file are two folds: +.. _pysidedeployspec_advantages: + #. Using the command line, you can control the deployment parameters without specifying them each - time.. It is saved permanently in a file, and any subsequent runs much later in time + time. It is saved permanently in a file, and any subsequent runs much later in time would enable the user to be aware of their last deployment parameters. #. Since these parameters are saved into a file, they can be checked into version control. This @@ -85,6 +96,9 @@ The relevant parameters for ``pyside6-deploy`` are: #qt-creator-python-project-file-pyproject>`_ file. Such a file makes sure that the deployment process never considers unnecessary files when bundling the executable. * ``exec_directory``: The directory where the final executable is generated. + * ``icon``: The icon used for the application. For Windows, the icon image should be of ``.ico`` + format, for macOS it should be of ``.icns`` format, and for linux all standard image formats + are accepted. **python** * ``python_path``: Path to the Python executable. It is recommended to run the deployment @@ -111,8 +125,28 @@ The relevant parameters for ``pyside6-deploy`` are: The reason why only the presence of the above 6 Qt modules is searched for is because they have the most size heavy binaries among all the Qt modules. With this, you can drastically reduce the size of your executables. + * ``modules``: Comma-separated list of all the Qt modules used by the application. Just like the + other configuration options in `pysidedeploy.spec`, this option is also computed automatically + by ``pyside6-deploy``. However, if the user wants to explicitly include certain Qt modules, the + module names can be appended to this list without the `Qt` prefix. + e.g. Network instead of QtNetwork + * ``plugins``: Comma-separated list of all the Qt plugins used by the application. Just like the + other configuration options in `pysidedeploy.spec`, this option is also computed automatically + by ``pyside6-deploy``. However, if the user wants to explicitly include certain Qt plugins, + the plugin names can be appended to this list. To see all the plugins bundled with PySide6, + see the `plugins` folder in the `site-packages` on your Python where PySide6 is installed. The + plugin name correspond to their folder name. **nuitka** + * ``macos.permissions``: Only relevant for macOS. This option lists the permissions used by the + macOS application, as found in the ``Info.plist`` file of the macOS application bundle, using + the so-called UsageDescription strings. The permissions are normally automatically found by + ``pyside6-deploy``. However the user can also explicitly specify them using the format + `:`. For example, the Camera permission is specified + as:: + + NSCameraUsageDescription:CameraAccess + * ``extra_args``: Any extra Nuitka arguments specified. It is specified as space-separated command line arguments i.e. just like how you would specify it when you use Nuitka through the command line. By default, it contains the following arguments:: @@ -142,9 +176,9 @@ Here are all the command line options of ``pyside6-deploy``: pyside6-deploy /path/to/main --init -* **-v/--verbose**: Runs ``pyside6-deploy`` in verbose mode +* **-v/--verbose**: Runs ``pyside6-deploy`` in verbose mode. -* **--dry-run**: Displays the final Nuitka command being run +* **--dry-run**: Displays the final Nuitka command being run. * **--keep-deployment-files**: When this option is added, it retains the build folders created by Nuitka during the deployment process. @@ -153,3 +187,34 @@ Here are all the command line options of ``pyside6-deploy``: ``pyside6-deploy`` prompts the user to create a Python virtual environment, if not already in one. With this option, the current Python environment is used irrespective of whether the current Python environment is a virtual environment or not. + +* **--name**: Application name. + +* **--extra-ignore-dirs**: Comma-separated directory names inside the project directory. These + directories will be skipped when searching for Python files relevant to the project. + +* **--extra-modules**: Comma-separated list of Qt modules to be added to the application, + in case they are not found automatically. The module name can either be specified + by omitting the prefix of Qt or including it eg: both Network and QtNetwork works. + +Considerations +=============== + +For deployment to work efficiently by bundling only the necessary plugins, the following utilities +are required to be installed on the system: + +.. list-table:: + :header-rows: 1 + + * - OS + - Dependencies + - Installation + * - Windows + - dumpbin + - Shipped with MSVC. Run `vcvarsall.bat` to add it to PATH + * - Linux + - readelf + - Available by default + * - macOS + - dyld_info + - Available by default from macOS 12 and upwards diff --git a/sources/pyside6/doc/deployment/index.rst b/sources/pyside6/doc/deployment/index.rst index 6f4c2d44..36e67756 100644 --- a/sources/pyside6/doc/deployment/index.rst +++ b/sources/pyside6/doc/deployment/index.rst @@ -17,8 +17,9 @@ Here are a few distribution options that you can use: If you are considering Option 3, then starting with 6.4, we ship a new tool called `pyside6-deploy` that deploys your PySide6 application to all desktop platforms - Windows, Linux, and macOS. To know -more about how to use the tool see :ref:`pyside6-deploy`. Additionally, you can also use other -popular deployment tools shown below: +more about how to use the tool see :ref:`pyside6-deploy`. For Android deployment, see +:ref:`pyside6-android-deploy`. Additionally, you can also use other popular deployment tools shown +below: * `fbs`_ * `PyInstaller`_ @@ -145,6 +146,7 @@ Here's a set of tutorials on how to use these tools: :maxdepth: 2 deployment-pyside6-deploy.rst + deployment-pyside6-android-deploy.rst deployment-fbs.rst deployment-pyinstaller.rst deployment-cxfreeze.rst diff --git a/sources/pyside6/doc/developer/adapt_qt.rst b/sources/pyside6/doc/developer/adapt_qt.rst index f8582f69..aec81fb0 100644 --- a/sources/pyside6/doc/developer/adapt_qt.rst +++ b/sources/pyside6/doc/developer/adapt_qt.rst @@ -3,6 +3,9 @@ Adapt to new Qt versions ======================== +Adapting to source changes +-------------------------- + The dev branch of PySide is switched to a new Qt minor version after its API review is finished and the API is stable. @@ -23,9 +26,9 @@ feature checks need to be added to ``CMakeList.txt`` (see for example The process consists of running a build and evaluating the log file. The script `shiboken2tasks.py `_ -from the Qt Creator repository can be used to convert the shiboken warnings +from the *Qt Creator* repository can be used to convert the shiboken warnings into a `task file `_ -for display in the build issues pane of Qt Creator. +for display in the build issues pane of *Qt Creator*. Warnings about new enumerations will be shown there; they should be added to type system file using a ``since`` attribute. @@ -41,3 +44,11 @@ The Qt source code should be checked for new overloads The resolution needs to be decided for each individual case, mostly by removing old functions and using ```` to declare new API. + +Bumping the version +------------------- + +To instruct ``COIN`` to use the next version of Qt, adapt the files +``coin/dependencies.yaml`` and/or ``product_dependencies.yaml`` accordingly. +Next, the wheel names should be changed by adapting +``sources/shiboken6/.cmake.conf`` and ``sources/pyside6/.cmake.conf``. diff --git a/sources/pyside6/doc/developer/add_module.rst b/sources/pyside6/doc/developer/add_module.rst index f75aa548..2eb96220 100644 --- a/sources/pyside6/doc/developer/add_module.rst +++ b/sources/pyside6/doc/developer/add_module.rst @@ -18,7 +18,7 @@ Add bindings - Add it to ``build_scripts/wheel_files.py`` (plugins, translations). - Copy an existing module to ``sources/pyside6/PySide6/``. - Adapt the ``typesystem.xml`` and ``CMakeList.txt`` (using for example - Qt Creator's case-preserving replace function). + *Qt Creator*'s case-preserving replace function). - Make sure the dependencies are correct. - Find the exported public classes, add them to the ``typesystem.xml`` file, checking whether they are ``value-type`` or ``object-type``. Add their enums @@ -33,8 +33,8 @@ Add bindings typically hidden behind a progress message. - A convenient way of doing this is using ``qt-creator/scripts/shiboken2tasks.py`` from the - `Qt Creator repository `_ - converting them to a ``.tasks`` file which can be loaded into Qt Creator's + `*Qt Creator* repository `_ + converting them to a ``.tasks`` file which can be loaded into *Qt Creator*'s issue pane. - Link errors may manifest when ``generate_pyi`` imports the module trying to create signatures. They indicate a missing source file entry diff --git a/sources/pyside6/doc/developer/add_tool.rst b/sources/pyside6/doc/developer/add_tool.rst index a894226c..732e6b91 100644 --- a/sources/pyside6/doc/developer/add_tool.rst +++ b/sources/pyside6/doc/developer/add_tool.rst @@ -44,6 +44,8 @@ Add a Qt tool wrapper - Add the tool in ``sources/pyside-tools/pyside_tool.py``. - Add the tool in ``build_scripts/__init__.py`` to create the setuptools entry points i.e. this enable using the tool from the console as "pyside6-" -- Add an entry to ``sources/pyside6/doc/gettingstarted/package_details.rst``. +- Add an entry to ``sources/pyside6/doc/tools/index.rst`` and the detailed + documentation to ``sources/pyside6/doc/tools/.rst``. - Include the necessary Qt binaries explicitly on ``build_scripts/wheel_files.py`` -- Build with ``--standalone``, verify it is working. +- Add the necessary files to ``build_scripts/wheel_files.py``. +- Build with ``--standalone``, verify it is working. Also, check if the wheel bundles the tool. diff --git a/sources/pyside6/doc/developer/extras.rst b/sources/pyside6/doc/developer/extras.rst index 79fc7219..9788b539 100644 --- a/sources/pyside6/doc/developer/extras.rst +++ b/sources/pyside6/doc/developer/extras.rst @@ -24,6 +24,19 @@ Build on the command line - Consider using ``build_scripts/qp5_tool.py``. +Build with address sanitizer (Linux) +==================================== + +ASAN needs to be told to not exit on memory leaks and its library +needs to be pre-loaded. Assuming the library is found +at ``/usr/lib/gcc/x86_64-linux-gnu/11``: + +.. code-block:: bash + + export ASAN_OPTIONS=detect_leaks=0 + export LD_PRELOAD=/usr/lib/gcc/x86_64-linux-gnu/11/libasan.so + python setup.py build [...] --sanitize-address + De-Virtualize the Python Files ============================== diff --git a/sources/pyside6/doc/extras/QtCore.ClassInfo.rst b/sources/pyside6/doc/extras/QtCore.ClassInfo.rst index aa080aa0..75445e1f 100644 --- a/sources/pyside6/doc/extras/QtCore.ClassInfo.rst +++ b/sources/pyside6/doc/extras/QtCore.ClassInfo.rst @@ -1,11 +1,9 @@ .. currentmodule:: PySide6.QtCore -.. _ClassInfo: +.. py:decorator:: ClassInfo -ClassInfo -********* - -This class is used to associate extra information to the class, which is available -using QObject.metaObject(). Qt and PySide doesn't use this information. +This decorator is used to associate extra information to the class, which is available +using ``QObject.metaObject()``. This information is used by the +*Qt D-Bus* and *Qt Qml* modules. The extra information takes the form of a dictionary with key and value in a literal string. @@ -16,7 +14,7 @@ If the key needs to contain special characters (spaces, commas, '::', start with it is also possible to pass a python dictionary with arbitrary strings for both the key and value and enabling special characters in the key. -.. note:: This Class is a implementation of Q_CLASSINFO macro. +.. note:: This decorator is a implementation of the Q_CLASSINFO macro. Example diff --git a/sources/pyside6/doc/extras/QtCore.Property.rst b/sources/pyside6/doc/extras/QtCore.Property.rst index 64567c4b..9ed7de42 100644 --- a/sources/pyside6/doc/extras/QtCore.Property.rst +++ b/sources/pyside6/doc/extras/QtCore.Property.rst @@ -1,8 +1,5 @@ .. currentmodule:: PySide6.QtCore -.. _Property: - -Property -******** +.. py:class:: Property Detailed Description -------------------- diff --git a/sources/pyside6/doc/extras/QtCore.QEnum.rst b/sources/pyside6/doc/extras/QtCore.QEnum.rst index d42da91a..7ea35d8a 100644 --- a/sources/pyside6/doc/extras/QtCore.QEnum.rst +++ b/sources/pyside6/doc/extras/QtCore.QEnum.rst @@ -1,23 +1,19 @@ .. currentmodule:: PySide6.QtCore -.. _QEnum: - -QEnum/QFlag -*********** - -This class decorator is equivalent to the `Q_ENUM` macro from Qt. -The decorator is used to register an Enum to the meta-object system, -which is available via `QObject.staticMetaObject`. -The enumerator must be in a QObject derived class to be registered. +.. py:decorator:: QEnum +This class decorator is equivalent to the `Q_ENUM` macro from Qt. The decorator +is used to register a Python Enum derived class to the meta-object system, +which is available via `QObject.staticMetaObject`. The enumerator must be in a +QObject derived class to be registered. Example ------- :: - from enum import Enum, Flag, auto + from enum import Enum, auto - from PySide6.QtCore import QEnum, QFlag, QObject + from PySide6.QtCore import QEnum, QObject class Demo(QObject): @@ -25,67 +21,7 @@ Example class Orientation(Enum): North, East, South, West = range(4) - class Color(Flag): - RED = auto() - BLUE = auto() - GREEN = auto() - WHITE = RED | BLUE | GREEN - - QFlag(Color) # identical to @QFlag usage - - -Caution: --------- - -QEnum registers a Python Enum derived class. -QFlag treats a variation of the Python Enum, the Flag class. - -Please do not confuse that with the Qt QFlags concept. Python does -not use that concept, it has its own class hierarchy, instead. -For more details, see the `Python enum documentation `_. - - -Details about Qt Flags: ------------------------ - -There are some small differences between Qt flags and Python flags. -In Qt, we have for instance these declarations: - -:: - - enum QtGui::RenderHint { Antialiasing, TextAntialiasing, SmoothPixmapTransform, - HighQualityAntialiasing, NonCosmeticDefaultPen } - flags QtGui::RenderHints - -The equivalent Python notation would look like this: - -:: - - @QFlag - class RenderHints(enum.Flag) - Antialiasing = auto() - TextAntialiasing = auto() - SmoothPixmapTransform = auto() - HighQualityAntialiasing = auto() - NonCosmeticDefaultPen = auto() - - -As another example, the Qt::AlignmentFlag flag has 'AlignmentFlag' as the enum -name, but 'Alignment' as the type name. Non flag enums have the same type and -enum names. - -:: - - enum Qt::AlignmentFlag - flags Qt::Alignment - -The Python way to specify this would be - -:: - - @QFlag - class Alignment(enum.Flag): - ... +See :deco:`QFlag` for registering Python Flag derived classes. -Meanwhile we have converted all enums and flags to Python Enums (optional in ``PySide 6.3``, -default in ``PySide 6.4``), see the :ref:`NewEnumSystem` section. +Meanwhile all enums and flags have been converted to Python Enums +(default since ``PySide 6.4``), see the :ref:`NewEnumSystem` section. diff --git a/sources/pyside6/doc/extras/QtCore.QFlag.rst b/sources/pyside6/doc/extras/QtCore.QFlag.rst new file mode 100644 index 00000000..dd4f0280 --- /dev/null +++ b/sources/pyside6/doc/extras/QtCore.QFlag.rst @@ -0,0 +1,74 @@ +.. currentmodule:: PySide6.QtCore +.. py:decorator:: QFlag + +QFlag handles a variation of the Python Enum, the Flag class. + +Please do not confuse that with the Qt QFlags concept. Python does +not use that concept, it has its own class hierarchy, instead. +For more details, see the `Python enum documentation `_. + +Example +------- + +:: + + from enum import Flag, auto + + from PySide6.QtCore import QFlag, QObject + + class Demo(QObject): + + @QFlag + class Color(Flag): + RED = auto() + BLUE = auto() + GREEN = auto() + WHITE = RED | BLUE | GREEN + + +Details about Qt Flags: +----------------------- + +There are some small differences between Qt flags and Python flags. +In Qt, we have for instance these declarations: + +:: + + enum QtGui::RenderHint { Antialiasing, TextAntialiasing, SmoothPixmapTransform, + HighQualityAntialiasing, NonCosmeticDefaultPen } + flags QtGui::RenderHints + +The equivalent Python notation would look like this: + +:: + + @QFlag + class RenderHints(enum.Flag) + Antialiasing = auto() + TextAntialiasing = auto() + SmoothPixmapTransform = auto() + HighQualityAntialiasing = auto() + NonCosmeticDefaultPen = auto() + + +As another example, the Qt::AlignmentFlag flag has 'AlignmentFlag' as the enum +name, but 'Alignment' as the type name. Non flag enums have the same type and +enum names. + +:: + + enum Qt::AlignmentFlag + flags Qt::Alignment + +The Python way to specify this would be + +:: + + @QFlag + class Alignment(enum.Flag): + ... + +See :deco:`QEnum` for registering Python Enum derived classes. + +Meanwhile all enums and flags have been converted to Python Enums +(default since ``PySide 6.4``), see the :ref:`NewEnumSystem` section. diff --git a/sources/pyside6/doc/extras/QtCore.Signal.rst b/sources/pyside6/doc/extras/QtCore.Signal.rst index 43dd4921..c83a76a8 100644 --- a/sources/pyside6/doc/extras/QtCore.Signal.rst +++ b/sources/pyside6/doc/extras/QtCore.Signal.rst @@ -1,8 +1,5 @@ .. currentmodule:: PySide6.QtCore -.. _Signal: - -Qt Signal -********* +.. py:class:: Signal Synopsis -------- diff --git a/sources/pyside6/doc/extras/QtCore.Slot.rst b/sources/pyside6/doc/extras/QtCore.Slot.rst index 06646e33..98a1d465 100644 --- a/sources/pyside6/doc/extras/QtCore.Slot.rst +++ b/sources/pyside6/doc/extras/QtCore.Slot.rst @@ -1,57 +1,42 @@ .. currentmodule:: PySide6.QtCore -.. _Slot: +.. py:decorator:: Slot([type1 [, type2...]] [, name="" [, result=None, [tag=""]]]) -Qt Slots -******** + :param name: str + :param result: type + :param tag: str -Detailed Description --------------------- +``Slot`` takes a list of Python types of the arguments. - PySide6 adopt PyQt's new signal and slot syntax as-is. The PySide6 - implementation is functionally compatible with the PyQt one, with the - exceptions listed below. +The optional named argument ``name`` defines the slot name. If nothing is +passed, the slot name will be the decorated function name. - PyQt's new signal and slot style utilizes method and decorator names - specific to their implementation. These will be generalized according to - the table below: +The optional named argument ``result`` specifies the return type. - ======= ======================= ============= - Module PyQt factory function PySide class - ======= ======================= ============= - QtCore pyqtSignal Signal - QtCore pyqtSlot Slot - ======= ======================= ============= +The optional named argument ``tag`` specifies a value to be returned +by ``QMetaMethod.tag()``. - .. class:: PySide6.QtCore.Slot([type1 [, type2...]] [, name="" [, result=None, [tag=""]]]) +This implementation is functionally compatible with the PyQt one. - :param name: str - :param result: type - :param tag: str +======= =========== ====== +Module PyQt PySide +======= =========== ====== +QtCore pyqtSignal Signal +QtCore pyqtSlot Slot +======= =========== ====== - ``Slot`` takes a list of Python types of the arguments. - - The optional named argument ``name`` defines the slot name. If nothing is - passed, the slot name will be the decorated function name. - - The optional named argument ``result`` specifies the return type. - - The optional named argument ``tag`` specifies a value to be returned - by ``QMetaMethod.tag()``. - - .. seealso:: :ref:`signals-and-slots` +.. seealso:: :ref:`signals-and-slots` Q_INVOKABLE ----------- - There is no equivalent of the Q_INVOKABLE macro of Qt - since PySide6 slots can actually have return values. - If you need to create a invokable method that returns some value, - declare it as a slot, e.g.: - - :: +There is no equivalent of the Q_INVOKABLE macro of Qt +since PySide6 slots can actually have return values. +If you need to create a invokable method that returns some value, +declare it as a slot, e.g.: - class Foo(QObject): +:: - @Slot(float, result=int) - def getFloatReturnInt(self, f): - return int(f) + class Foo(QObject): + @Slot(float, result=int) + def getFloatReturnInt(self, f): + return int(f) diff --git a/sources/pyside6/doc/extras/QtDesigner.QPyDesignerContainerExtension.rst b/sources/pyside6/doc/extras/QtDesigner.QPyDesignerContainerExtension.rst index 5280e5e2..a3e5ef51 100644 --- a/sources/pyside6/doc/extras/QtDesigner.QPyDesignerContainerExtension.rst +++ b/sources/pyside6/doc/extras/QtDesigner.QPyDesignerContainerExtension.rst @@ -1,12 +1,9 @@ .. currentmodule:: PySide6.QtDesigner -.. _QPyDesignerContainerExtension: - -QPyDesignerContainerExtension -***************************** +.. py:class:: QPyDesignerContainerExtension QPyDesignerContainerExtension is the base class for implementing `QDesignerContainerExtension class`_ -for a Qt Designer custom widget plugin in Python. +for a *Qt Widgets Designer* custom widget plugin in Python. It provides the required inheritance from **QObject**. .. _QDesignerContainerExtension class: https://doc.qt.io/qt-6/qdesignercontainerextension.html diff --git a/sources/pyside6/doc/extras/QtDesigner.QPyDesignerCustomWidgetCollection.rst b/sources/pyside6/doc/extras/QtDesigner.QPyDesignerCustomWidgetCollection.rst index 71ef6cd6..615ca4ee 100644 --- a/sources/pyside6/doc/extras/QtDesigner.QPyDesignerCustomWidgetCollection.rst +++ b/sources/pyside6/doc/extras/QtDesigner.QPyDesignerCustomWidgetCollection.rst @@ -1,8 +1,5 @@ .. currentmodule:: PySide6.QtDesigner -.. _QPyDesignerCustomWidgetCollection: - -QPyDesignerCustomWidgetCollection -********************************* +.. py:class:: QPyDesignerCustomWidgetCollection Synopsis -------- @@ -29,7 +26,8 @@ The usage is explained in :ref:`designer_custom_widgets`. .. py:staticmethod:: QPyDesignerCustomWidgetCollection.registerCustomWidget(type[, xml=""[, tool_tip=""[, icon=""[, group=""[container=False]]]]]) - Registers an instance of a Python-implemented QWidget by type with Qt Designer. + Registers an instance of a Python-implemented QWidget by type with + *Qt Widgets Designer*. The optional keyword arguments correspond to the getters of `QDesignerCustomWidgetInterface`_ : @@ -47,7 +45,7 @@ The usage is explained in :ref:`designer_custom_widgets`. Adds a custom widget (implementation of `QDesignerCustomWidgetInterface`_) - with Qt Designer. + with *Qt Widgets Designer*. :param QDesignerCustomWidgetInterface custom_widget: Custom widget instance diff --git a/sources/pyside6/doc/extras/QtDesigner.QPyDesignerMemberSheetExtension.rst b/sources/pyside6/doc/extras/QtDesigner.QPyDesignerMemberSheetExtension.rst index 997da148..ef235622 100644 --- a/sources/pyside6/doc/extras/QtDesigner.QPyDesignerMemberSheetExtension.rst +++ b/sources/pyside6/doc/extras/QtDesigner.QPyDesignerMemberSheetExtension.rst @@ -1,12 +1,9 @@ .. currentmodule:: PySide6.QtDesigner -.. _QPyDesignerMemberSheetExtension: - -QPyDesignerMemberSheetExtension -******************************* +.. py:class:: QPyDesignerMemberSheetExtension QPyDesignerMemberSheetExtension is the base class for implementing `QDesignerMemberSheetExtension class`_ -for a Qt Designer custom widget plugin in Python. +for a *Qt Widgets Designer* custom widget plugin in Python. It provides the required inheritance from **QObject**. .. _QDesignerMemberSheetExtension class: https://doc.qt.io/qt-6/qdesignermembersheetextension.html diff --git a/sources/pyside6/doc/extras/QtDesigner.QPyDesignerTaskMenuExtension.rst b/sources/pyside6/doc/extras/QtDesigner.QPyDesignerTaskMenuExtension.rst index 84d19c54..e5e13122 100644 --- a/sources/pyside6/doc/extras/QtDesigner.QPyDesignerTaskMenuExtension.rst +++ b/sources/pyside6/doc/extras/QtDesigner.QPyDesignerTaskMenuExtension.rst @@ -1,12 +1,9 @@ .. currentmodule:: PySide6.QtDesigner -.. _QPyDesignerTaskMenuExtension: - -QPyDesignerTaskMenuExtension -**************************** +.. py:class:: QPyDesignerTaskMenuExtension QPyDesignerTaskMenuExtension is the base class for implementing `QDesignerTaskMenuExtension class`_ -for a Qt Designer custom widget plugin in Python. +for a *Qt Widgets Designer* custom widget plugin in Python. It provides the required inheritance from **QObject**. .. _QDesignerTaskMenuExtension class: https://doc.qt.io/qt-6/qdesignertaskmenuextension.html diff --git a/sources/pyside6/doc/extras/QtDesigner.rst b/sources/pyside6/doc/extras/QtDesigner.rst index c232128d..844f25b7 100644 --- a/sources/pyside6/doc/extras/QtDesigner.rst +++ b/sources/pyside6/doc/extras/QtDesigner.rst @@ -1,5 +1,6 @@ -Provides classes to create your own custom widget plugins for Qt Designer and -classes to access Qt Designer components. +Provides classes to create your own custom widget plugins for +*Qt Widgets Designer* and classes to access *Qt Widgets Designer* +components. In addition, the :class:`QFormBuilder` class provides the possibility of constructing user interfaces from UI files at diff --git a/sources/pyside6/doc/extras/QtQml.ListProperty.rst b/sources/pyside6/doc/extras/QtQml.ListProperty.rst new file mode 100644 index 00000000..eaa580c6 --- /dev/null +++ b/sources/pyside6/doc/extras/QtQml.ListProperty.rst @@ -0,0 +1,24 @@ +.. currentmodule:: PySide6.QtQml +.. py:class:: ListProperty + + The ``ListProperty`` class allows applications to expose list-like properties of + :class:`~PySide6.QtCore.QObject`-derived classes to QML. + The usage is shown in the :ref:`qml-object-and-list-property-types-example` + and the :ref:`qml-chapter5-listproperties` example. + + .. py:method:: __init__(type, append, count=None, at=None, clear=None, removeLast=None, doc="", notify=None, designable=True, scriptable=True, stored=True, user=False, constant=False, final=False) + + :param type type: Element type + :param callable append: A function to append an item + :param callable count: A function returning the list count + :param callable at: A function returning the item at an index + :param callable clear: A function to clear the list + :param removeLast: A function to remove the last item + :param str doc: Doc string + :param Signal notify: A signal emitted when a change occurs + :param bool designable: Not used in QML + :param bool scriptable: Not used in QML + :param bool stored: Whether the property is stored + :param bool user: Not used in QML + :param bool constant: Whether the property is constant + :param bool final: Whether the property is final diff --git a/sources/pyside6/doc/extras/QtQml.QPyQmlParserStatus.rst b/sources/pyside6/doc/extras/QtQml.QPyQmlParserStatus.rst index f027885a..10dde6b9 100644 --- a/sources/pyside6/doc/extras/QtQml.QPyQmlParserStatus.rst +++ b/sources/pyside6/doc/extras/QtQml.QPyQmlParserStatus.rst @@ -1,8 +1,5 @@ .. currentmodule:: PySide6.QtQml -.. _QPyQmlParserStatus: - -QPyQmlParserStatus -****************** +.. py:class:: QPyQmlParserStatus QPyQmlParserStatus is the base class for implementing `QQmlParserStatus class`_ . diff --git a/sources/pyside6/doc/extras/QtQml.QPyQmlPropertyValueSource.rst b/sources/pyside6/doc/extras/QtQml.QPyQmlPropertyValueSource.rst index fea23882..bf7f8e98 100644 --- a/sources/pyside6/doc/extras/QtQml.QPyQmlPropertyValueSource.rst +++ b/sources/pyside6/doc/extras/QtQml.QPyQmlPropertyValueSource.rst @@ -1,8 +1,5 @@ .. currentmodule:: PySide6.QtQml -.. _QPyQmlPropertyValueSource: - -QPyQmlPropertyValueSource -************************* +.. py:class:: QPyQmlPropertyValueSource QPyQmlPropertyValueSource is the base class for implementing `QQmlPropertyValueSource class`_ . diff --git a/sources/pyside6/doc/extras/QtQml.QmlAnonymous.rst b/sources/pyside6/doc/extras/QtQml.QmlAnonymous.rst index a805b076..44aa0ea2 100644 --- a/sources/pyside6/doc/extras/QtQml.QmlAnonymous.rst +++ b/sources/pyside6/doc/extras/QtQml.QmlAnonymous.rst @@ -1,22 +1,17 @@ .. currentmodule:: PySide6.QtQml -.. _QmlAnonymous: - -QmlAnonymous -************ - .. py:decorator:: QmlAnonymous - Declares the enclosing type to be available, but anonymous in QML. The type - cannot be created or used to declare properties in QML, but when passed from - C++, it is recognized. In QML, you can use properties of this type if they - are declared in C++. +Declares the enclosing type to be available, but anonymous in QML. The type +cannot be created or used to declare properties in QML, but when passed from +C++, it is recognized. In QML, you can use properties of this type if they +are declared in C++. - .. code-block:: python +.. code-block:: python - QML_IMPORT_NAME = "com.library.name" - QML_IMPORT_MAJOR_VERSION = 1 - QML_IMPORT_MINOR_VERSION = 0 # Optional + QML_IMPORT_NAME = "com.library.name" + QML_IMPORT_MAJOR_VERSION = 1 + QML_IMPORT_MINOR_VERSION = 0 # Optional - @QmlAnonymous - class ClassForQml(QObject): - # ... + @QmlAnonymous + class ClassForQml(QObject): + # ... diff --git a/sources/pyside6/doc/extras/QtQml.QmlAttached.rst b/sources/pyside6/doc/extras/QtQml.QmlAttached.rst index e3fefb6b..4331b03f 100644 --- a/sources/pyside6/doc/extras/QtQml.QmlAttached.rst +++ b/sources/pyside6/doc/extras/QtQml.QmlAttached.rst @@ -1,9 +1,4 @@ .. currentmodule:: PySide6.QtQml -.. _QmlAttached: - -QmlAttached -*********** - .. py:decorator:: QmlAttached This decorator declares that the enclosing type attaches the type passed as diff --git a/sources/pyside6/doc/extras/QtQml.QmlElement.rst b/sources/pyside6/doc/extras/QtQml.QmlElement.rst index 2746fbc0..66397b2d 100644 --- a/sources/pyside6/doc/extras/QtQml.QmlElement.rst +++ b/sources/pyside6/doc/extras/QtQml.QmlElement.rst @@ -1,30 +1,25 @@ .. currentmodule:: PySide6.QtQml -.. _QmlElement: - -QmlElement -********** - .. py:decorator:: QmlElement - This decorator registers a class it is attached to for use in QML, using - global variables to specify the import name and version. +This decorator registers a class it is attached to for use in QML, using +global variables to specify the import name and version. - .. code-block:: python +.. code-block:: python - QML_IMPORT_NAME = "com.library.name" - QML_IMPORT_MAJOR_VERSION = 1 - QML_IMPORT_MINOR_VERSION = 0 # Optional + QML_IMPORT_NAME = "com.library.name" + QML_IMPORT_MAJOR_VERSION = 1 + QML_IMPORT_MINOR_VERSION = 0 # Optional - @QmlElement - class ClassForQml(QObject): - # ... + @QmlElement + class ClassForQml(QObject): + # ... - Afterwards the class may be used in QML: +Afterwards the class may be used in QML: - .. code-block:: python +.. code-block:: python - import com.library.name 1.0 + import com.library.name 1.0 - ClassForQml { - // ... - } + ClassForQml { + // ... + } diff --git a/sources/pyside6/doc/extras/QtQml.QmlExtended.rst b/sources/pyside6/doc/extras/QtQml.QmlExtended.rst index f9ed5a10..af113a9c 100644 --- a/sources/pyside6/doc/extras/QtQml.QmlExtended.rst +++ b/sources/pyside6/doc/extras/QtQml.QmlExtended.rst @@ -1,9 +1,4 @@ .. currentmodule:: PySide6.QtQml -.. _QmlExtended: - -QmlExtended -*********** - .. py:decorator:: QmlExtended Declares that the enclosing type uses the type passed as an extension to diff --git a/sources/pyside6/doc/extras/QtQml.QmlForeign.rst b/sources/pyside6/doc/extras/QtQml.QmlForeign.rst index c58be3cb..90b821e9 100644 --- a/sources/pyside6/doc/extras/QtQml.QmlForeign.rst +++ b/sources/pyside6/doc/extras/QtQml.QmlForeign.rst @@ -1,33 +1,28 @@ .. currentmodule:: PySide6.QtQml -.. _QmlForeign: - -QmlForeign -********** - .. py:decorator:: QmlForeign - This decorator can be used to change the type that is created by QML. +This decorator can be used to change the type that is created by QML. - This is useful for registering types that cannot be amended by adding the - QmlElement decorator, for example because they belong to 3rdparty libraries. +This is useful for registering types that cannot be amended by adding the +QmlElement decorator, for example because they belong to 3rdparty libraries. - .. code-block:: python +.. code-block:: python - QML_IMPORT_NAME = "com.library.name" - QML_IMPORT_MAJOR_VERSION = 1 - QML_IMPORT_MINOR_VERSION = 0 # Optional + QML_IMPORT_NAME = "com.library.name" + QML_IMPORT_MAJOR_VERSION = 1 + QML_IMPORT_MINOR_VERSION = 0 # Optional - @QmlNamedElement("QWidget") - @QmlForeign(QWidget) - class ForeignWidgetHelperClass(QObject): + @QmlNamedElement("QWidget") + @QmlForeign(QWidget) + class ForeignWidgetHelperClass(QObject): ... - Afterwards the class may be used in QML: +Afterwards the class may be used in QML: - .. code-block:: javascript +.. code-block:: javascript - import com.library.name 1.0 + import com.library.name 1.0 - QWidget { - // ... - } + QWidget { + // ... + } diff --git a/sources/pyside6/doc/extras/QtQml.QmlNamedElement.rst b/sources/pyside6/doc/extras/QtQml.QmlNamedElement.rst index d603e2e8..79eb9d7a 100644 --- a/sources/pyside6/doc/extras/QtQml.QmlNamedElement.rst +++ b/sources/pyside6/doc/extras/QtQml.QmlNamedElement.rst @@ -1,31 +1,26 @@ .. currentmodule:: PySide6.QtQml -.. _QmlNamedElement: - -QmlNamedElement -*************** - .. py:decorator:: QmlNamedElement - This decorator registers a class it is attached to for use in QML under - a name different from the class name, using global variables to specify - the import name and version. +This decorator registers a class it is attached to for use in QML under +a name different from the class name, using global variables to specify +the import name and version. - .. code-block:: python +.. code-block:: python - QML_IMPORT_NAME = "com.library.name" - QML_IMPORT_MAJOR_VERSION = 1 - QML_IMPORT_MINOR_VERSION = 0 # Optional + QML_IMPORT_NAME = "com.library.name" + QML_IMPORT_MAJOR_VERSION = 1 + QML_IMPORT_MINOR_VERSION = 0 # Optional - @QmlNamedElement("ClassForQml") - class ClassWithSomeName(QObject): + @QmlNamedElement("ClassForQml") + class ClassWithSomeName(QObject): ... - Afterwards the class may be used in QML: +Afterwards the class may be used in QML: - .. code-block:: javascript +.. code-block:: javascript - import com.library.name 1.0 + import com.library.name 1.0 - ClassForQml { - // ... - } + ClassForQml { + // ... + } diff --git a/sources/pyside6/doc/extras/QtQml.QmlSingleton.rst b/sources/pyside6/doc/extras/QtQml.QmlSingleton.rst index a2430389..402c18d5 100644 --- a/sources/pyside6/doc/extras/QtQml.QmlSingleton.rst +++ b/sources/pyside6/doc/extras/QtQml.QmlSingleton.rst @@ -1,13 +1,8 @@ .. currentmodule:: PySide6.QtQml -.. _QmlSingleton: - -QmlSingleton decorator -********************** - .. py:decorator:: QmlSingleton Declares the decorated type to be a singleton in QML. This only takes effect if -the type is a Q_OBJECT and is available in QML (by having a QmlElement decorator). +the type is a QObject and is available in QML (by having a QmlElement decorator). The QQmlEngine will try to create a singleton instance using the type's default constructor. @@ -22,4 +17,17 @@ constructor. class ClassForQml(QObject): ... +It is also possible to use a static ``create()`` method which receives +the engine as a parameter: + +.. code-block:: python + + @QmlElement + @QmlSingleton + class ClassForQml(QObject): + + @staticmethod + def create(engine): + ... + .. note:: The order of the decorators matters; ``QmlSingleton`` needs to be preceded by ``QmlElement``. diff --git a/sources/pyside6/doc/extras/QtQml.QmlUncreatable.rst b/sources/pyside6/doc/extras/QtQml.QmlUncreatable.rst index 46c7c49d..b7a28801 100644 --- a/sources/pyside6/doc/extras/QtQml.QmlUncreatable.rst +++ b/sources/pyside6/doc/extras/QtQml.QmlUncreatable.rst @@ -1,9 +1,4 @@ .. currentmodule:: PySide6.QtQml -.. _QmlUncreatable: - -QmlUncreatable -************** - .. py:decorator:: QmlUncreatable Declares that the decorated type shall not be creatable from QML. This takes diff --git a/sources/pyside6/doc/extras/QtQml.qmlRegisterSingletonInstance.rst b/sources/pyside6/doc/extras/QtQml.qmlRegisterSingletonInstance.rst deleted file mode 100644 index 19d59893..00000000 --- a/sources/pyside6/doc/extras/QtQml.qmlRegisterSingletonInstance.rst +++ /dev/null @@ -1,25 +0,0 @@ -.. currentmodule:: PySide6.QtQml -.. _qmlRegisterSingletonInstance: - -qmlRegisterSingletonInstance -**************************** - -.. py:function:: qmlRegisterSingletonInstance(pytype: type,\ - uri: str,\ - versionMajor: int,\ - versionMinor: int,\ - typeName: str,\ - instanceObject: object) -> int - - :param type pytype: Python class - :param str uri: uri to use while importing the component in QML - :param int versionMajor: major version - :param int versionMinor: minor version - :param str typeName: name exposed to QML - :param object instanceObject: singleton object to be registered - :return: int (the QML type id) - - This function registers a singleton Python object *instanceObject*, with a particular *uri* and - *typeName*. Its version is a combination of *versionMajor* and *versionMinor*. - - Use this function to register an object of the given type *pytype* as a singleton type. diff --git a/sources/pyside6/doc/extras/QtQml.qmlRegisterSingletonType.rst b/sources/pyside6/doc/extras/QtQml.qmlRegisterSingletonType.rst deleted file mode 100644 index 32231a39..00000000 --- a/sources/pyside6/doc/extras/QtQml.qmlRegisterSingletonType.rst +++ /dev/null @@ -1,46 +0,0 @@ -.. currentmodule:: PySide6.QtQml -.. _qmlRegisterSingletonType: - -qmlRegisterSingletonType -************************ - -.. py:function:: qmlRegisterSingletonType(pytype: type, uri: str, versionMajor: int, versionMinor: int, typeName: str) -> int - - :param type pytype: Python class - :param str uri: uri to use while importing the component in QML - :param int versionMajor: major version - :param int versionMinor: minor version - :param str typeName: name exposed to QML - :return: int (the QML type id) - - This function registers a Python type as a singleton in the QML system. - - Alternatively, the :ref:`QmlSingleton` decorator can be used. - -.. py:function:: qmlRegisterSingletonType(pytype: type, uri: str, versionMajor: int, versionMinor: int, typeName: str, callback: object) -> int - - :param type pytype: Python class - :param str uri: uri to use while importing the component in QML - :param int versionMajor: major version - :param int versionMinor: minor version - :param str typeName: name exposed to QML - :param object callback: Python callable (to handle Python type) - :return: int (the QML type id) - - This function registers a Python type as a singleton in the QML system using - the provided callback (which gets a QQmlEngine as a parameter) to generate - the singleton. - - -.. py:function:: qmlRegisterSingletonType(uri: str, versionMajor: int, versionMinor: int, typeName: str, callback: object) -> int - - :param str uri: uri to use while importing the component in QML - :param int versionMajor: major version - :param int versionMinor: minor version - :param str typeName: name exposed to QML - :param object callback: Python callable (to handle QJSValue) - :return: int (the QML type id) - - This function registers a QJSValue as a singleton in the QML system using - the provided callback (which gets a QQmlEngine as a parameter) to - generate the singleton. diff --git a/sources/pyside6/doc/extras/QtQml.qmlRegisterType.rst b/sources/pyside6/doc/extras/QtQml.qmlRegisterType.rst deleted file mode 100644 index 079e15bf..00000000 --- a/sources/pyside6/doc/extras/QtQml.qmlRegisterType.rst +++ /dev/null @@ -1,41 +0,0 @@ -.. currentmodule:: PySide6.QtQml -.. _qmlRegisterType: - -qmlRegisterType -*************** - -.. py:function:: qmlRegisterType(pytype: type, uri: str, versionMajor: int, versionMinor: int, qmlName: str) -> int - - :param type pytype: Python class - :param str uri: uri to use while importing the component in QML - :param int versionMajor: major version - :param int versionMinor: minor version - :param str qmlName: name exposed to QML - :return: int (the QML type id) - - This function registers the Python *type* in the QML system with the - name *qmlName*, in the library imported from *uri* having the - version number composed from *versionMajor* and *versionMinor*. - - For example, this registers a Python class 'MySliderItem' as a QML - type named 'Slider' for version '1.0' of a module called - 'com.mycompany.qmlcomponents': - - :: - - qmlRegisterType(MySliderItem, "com.mycompany.qmlcomponents", 1, 0, "Slider") - - Once this is registered, the type can be used in QML by importing - the specified module name and version number: - - :: - - import com.mycompany.qmlcomponents 1.0 - - Slider { ... } - - Note that it's perfectly reasonable for a library to register types - to older versions than the actual version of the library. - Indeed, it is normal for the new library to allow QML written to - previous versions to continue to work, even if more advanced - versions of some of its types are available. diff --git a/sources/pyside6/doc/extras/QtQml.qmlRegisterUncreatableType.rst b/sources/pyside6/doc/extras/QtQml.qmlRegisterUncreatableType.rst deleted file mode 100644 index be25f5f1..00000000 --- a/sources/pyside6/doc/extras/QtQml.qmlRegisterUncreatableType.rst +++ /dev/null @@ -1,38 +0,0 @@ -.. currentmodule:: PySide6.QtQml -.. _qmlRegisterUncreatableType: - - -qmlRegisterUncreatableType -************************** - - -.. py:function:: qmlRegisterUncreatableType(pytype: type, uri: str, versionMajor: int, versionMinor: int, qmlName: str, noCreationReason: str) -> int - - - :param type pytype: Python class - :param str uri: uri to use while importing the component in QML - :param int versionMajor: major version - :param int versionMinor: minor version - :param str qmlName: name exposed to QML - :param str noCreationReason: Error message shown when trying to create the QML type - :return: int (the QML type id) - - This function registers the Python *type* in the QML system as an uncreatable type with the - name *qmlName*, in the library imported from *uri* having the - version number composed from *versionMajor* and *versionMinor*, - showing *noCreationReason* as an error message when creating the type is attempted. - - For example, this registers a Python class 'MySliderItem' as a QML - type named 'Slider' for version '1.0' of a module called - 'com.mycompany.qmlcomponents': - - :: - qmlRegisterUncreatableType(MySliderItem, "com.mycompany.qmlcomponents", 1, 0, "Slider", "Slider cannot be created.") - - Note that it's perfectly reasonable for a library to register types - to older versions than the actual version of the library. - Indeed, it is normal for the new library to allow QML written to - previous versions to continue to work, even if more advanced - versions of some of its types are available. - - Alternatively, the :ref:`QmlUncreatable` decorator can be used. diff --git a/sources/pyside6/doc/extras/QtQuickTest.rst b/sources/pyside6/doc/extras/QtQuickTest.rst new file mode 100644 index 00000000..52f13590 --- /dev/null +++ b/sources/pyside6/doc/extras/QtQuickTest.rst @@ -0,0 +1,58 @@ + Qt Quick Test is a unit test framework for QML applications. Test cases are + written as JavaScript functions within a QML TestCase type: + +.. code-block:: JavaScript + + import QtQuick + import QtTest + + TestCase { + name: "MathTests" + + function test_math() { + compare(2 + 2, 4, "2 + 2 = 4") + } + + function test_fail() { + compare(2 + 2, 5, "2 + 2 = 5") + } + } + +Functions whose names start with ``test_`` are treated as test cases to be +executed. + +QML API +^^^^^^^ + +The `QML types `_ +in Qt Quick Test are available through the ``QtTest`` import. +To use the types, add the following import statement to your ``.qml`` file: + +.. code-block:: JavaScript + + import QtTest + +Running Tests +^^^^^^^^^^^^^ + +Test cases are launched by a harness that consists of the following code: + +.. code-block:: Python + + import sys + from PySide6.QtQuickTest import QUICK_TEST_MAIN + + QUICK_TEST_MAIN("example", sys.argv) + +Where "example" is the identifier to use to uniquely identify this set of +tests. + +Test execution can be controlled by a number of command line options (pass +``-h`` for help). + +Executing Code Before QML Tests +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To execute code before any of the QML tests are run, the +:py:func:`QUICK_TEST_MAIN_WITH_SETUP` function can be used. This can be useful +for setting context properties on the QML engine, amongst other things. diff --git a/sources/pyside6/doc/extras/QtUiTools.loadUiType.rst b/sources/pyside6/doc/extras/QtUiTools.loadUiType.rst deleted file mode 100644 index 48879d26..00000000 --- a/sources/pyside6/doc/extras/QtUiTools.loadUiType.rst +++ /dev/null @@ -1,35 +0,0 @@ -.. currentmodule:: PySide6.QtUiTools - -loadUiType -*********** - -.. py:function:: loadUiType(uifile: str) -> tuple(object, object) - - :param str uifile: The name of the `.ui` file - :return: tuple(object, object) - - This function generates and loads a `.ui` file at runtime, and it returns - a `tuple` containing the reference to the Python class, and the base class. - - We recommend not to use this approach as the workflow should be to generate a Python file - from the `.ui` file, and then import and load it to use it, but we do understand that - there are some corner cases when such functionality is required. - - The internal process relies on `uic` being in the PATH. - The `pyside6-uic` wrapper uses a shipped `uic` that is located in the - `site-packages/PySide6/uic`, so PATH needs to be updated to use that if there - is no `uic` in the system. - - A simple use case is:: - - from PySide6.QtUiTools import loadUiType - - generated_class, base_class = loadUiType("themewidget.ui") - # the values will be: - # (, ) - - widget = base_class() - form = generated_class() - form.setupUi(widget) - # form.a_widget_member.a_method_of_member() - widget.show() diff --git a/sources/pyside6/doc/extras/QtUiTools.rst b/sources/pyside6/doc/extras/QtUiTools.rst index c4873b43..e83aa035 100644 --- a/sources/pyside6/doc/extras/QtUiTools.rst +++ b/sources/pyside6/doc/extras/QtUiTools.rst @@ -1,4 +1,4 @@ -Qt Designer forms are processed at run-time to produce +*Qt Widgets Designer* forms are processed at run-time to produce dynamically-generated user interfaces. In order to generate a form at run-time, a resource file containing a UI file is needed. diff --git a/sources/pyside6/doc/extras/QtWidgets.rst b/sources/pyside6/doc/extras/QtWidgets.rst index 8c91d3e3..784421aa 100644 --- a/sources/pyside6/doc/extras/QtWidgets.rst +++ b/sources/pyside6/doc/extras/QtWidgets.rst @@ -52,7 +52,7 @@ widget reports its size requirements to the layout through the :meth:`sizePolicy` properties, and the layout distributes the available space accordingly. -:ref:`Qt Designer` is a powerful tool for interactively +:ref:`Qt Widgets Designer` is a powerful tool for interactively creating and arranging widgets in layouts. Model/View Classes diff --git a/sources/pyside6/doc/faq/typesoffiles.rst b/sources/pyside6/doc/faq/typesoffiles.rst index d3bd4512..3155b24f 100644 --- a/sources/pyside6/doc/faq/typesoffiles.rst +++ b/sources/pyside6/doc/faq/typesoffiles.rst @@ -36,9 +36,10 @@ functionality to your applications. User Interface Definition File ``.ui`` -------------------------------------- -When using Qt Designer, you can create user interfaces using Qt Widgets with -the WYSIWYG form editor, this interface is represented as a widget tree using -XML. Here is an extract of the beginning of a ``.ui`` file: +When using *Qt Widgets Designer*, you can create user interfaces using +Qt Widgets with the WYSIWYG form editor, this interface is represented +as a widget tree using XML. Here is an extract of the beginning of a +``.ui`` file: .. code-block:: xml @@ -96,8 +97,8 @@ the QML file, and optionally, elements defined in Python that are exposed to QML to be used. You can write ``.qml`` files by hand, but also you can use tools like the -QML Designer that is embedded in Qt Creator. Additionally, there are commercial -tools like Qt Design Studio that allow you to load designs from other design +*QML Designer* that is embedded in *Qt Creator*. Additionally, there are commercial +tools like *Qt Design Studio* that allow you to load designs from other design applications. Here you can find an example of how a ``.qml`` file looks like. @@ -127,11 +128,11 @@ message on it. Qt Creator Python Project File ``.pyproject`` --------------------------------------------- -For Qt Creator to load and handle Python based projects, a special file is +For *Qt Creator* to load and handle Python based projects, a special file is needed, because C++ based projects could be handle from ``.qmake`` or ``CMakeLists.txt`` file, which are not used with Python-based projects. -Old versions of Qt Creator, provided a simple format with the ``.pyqtc`` +Old versions of *Qt Creator*, provided a simple format with the ``.pyqtc`` extension, which were plain-text files with one-file-per-line:: library/server.py diff --git a/sources/pyside6/doc/faq/whatisqt.rst b/sources/pyside6/doc/faq/whatisqt.rst index 2f8cfd43..3b42a940 100644 --- a/sources/pyside6/doc/faq/whatisqt.rst +++ b/sources/pyside6/doc/faq/whatisqt.rst @@ -65,8 +65,8 @@ is not to affect the user experience compared to other included applications. .. note:: You can adapt these applications to use your self-made style, but you need to be aware that the goal of Widgets is to respect the system - style, be careful when changing colors. Check this `simple tutorial - `_ on how to do so. + style, be careful when changing colors. Check this + :ref:`simple tutorial ` on how to do so. QML --- diff --git a/sources/pyside6/doc/faq/whichide.rst b/sources/pyside6/doc/faq/whichide.rst index ec005a18..3afa79b7 100644 --- a/sources/pyside6/doc/faq/whichide.rst +++ b/sources/pyside6/doc/faq/whichide.rst @@ -4,7 +4,7 @@ Which IDEs Are Compatible? ========================== |project|, as any other Python module, can be used in any Python-compatible -IDE, but not all of them will provide extra functionality like Qt Creator does. +IDE, but not all of them will provide extra functionality like *Qt Creator* does. Besides writing files, there are some external steps you might want to perform in order to help the development of your applications: @@ -15,8 +15,8 @@ From a terminal: :command:`pyside6-uic -i form.ui -o ui_form.py` * Generating a Python file from a ``.qrc`` file: :command:`pyside6-rcc -i resources.qrc -o rc_resources.py` -* Opening Qt Designer with the command :command:`pyside6-designer` to - edit/create ``.ui`` files. +* Opening *Qt Widgets Designer* with the command :command:`pyside6-designer` + to edit/create ``.ui`` files. External add-ons/plugins from your favorite IDE might include configuration steps to run these commands, or open external tools like Designer and @@ -47,8 +47,10 @@ that you can install from VS Code while writing the following on the Quick Open PyCharm ------- -You can configure PyCharm to enable external tools, in |project| terms, Qt Designer, and -Qt Creator. Go to ``File > Settings > tools > PyCharm External Tools``, and include the following +You can configure PyCharm to enable external tools, in |project| terms, +*Qt Widgets Designer*, and *Qt Creator*. Go to +``File > Settings > tools > PyCharm External Tools``, and include the following information to add them to your project. -Later, you will be able to right click a ``.ui`` file, and select ``Qt Designer``, -``pyside6-uic``, or any tool that you configured this way. +Later, you will be able to right click a ``.ui`` file, and select +``Qt Widgets Designer``, ``pyside6-uic``, or any tool that you configured this +way. diff --git a/sources/pyside6/doc/gettingstarted/index.rst b/sources/pyside6/doc/gettingstarted/index.rst index 39a7f634..9d36061a 100644 --- a/sources/pyside6/doc/gettingstarted/index.rst +++ b/sources/pyside6/doc/gettingstarted/index.rst @@ -17,7 +17,7 @@ On **Linux** you might get them with your operating system package manager, on * you might get them with ``brew``, and on **Windows** you can download the installer from each website. -* **Python**: 3.7+ `[official Python website] `_ +* **Python**: 3.9+ `[official Python website] `_ * **Qt:** 6.4+ `[online installer] `_ * **CMake:** 3.18+ `[official CMake website] `_ * **Git:** 2.0+. `[official Git website] `_ @@ -104,9 +104,9 @@ using **ninja** (instead of make), and considering only the **module subset** of :mod:`QtCore `, :mod:`QtGui `, and :mod:`QtWidgets `. +`CMake Unity Build Mode`_ is used by default for speed-up. + Other important options to consider are: - * ``--unity``, Activates `CMake Unity Build Mode`_, which speeds up the - build by concatenating source files, * ``--cmake``, to specify the path to the cmake binary, * ``--reuse-build``, to rebuild only the modified files, * ``--openssl=/path/to/openssl/bin``, to use a different path for OpenSSL, @@ -416,16 +416,17 @@ You can find the ``requirements-doc.txt`` file in the root of the repository. Starting from 5.15, there are two options to build the documentation: -1. Building rst-only documentation (no API) +1. Building the base documentation (no API) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The process of parsing Qt headers to generate the PySide API documentation can take several -minutes, this means that modifying a specific section of the rst files we currently have, might -become a hard task. You may only care about the ReStructuredText only documentation. +minutes, this means that modifying a specific section of the documentation we currently have, might +become a hard task. You may only care about the base documentation, which comprises all the +documentation except for the API documentation. To generate this, execute the following command:: - python setup.py build_rst_docs + python setup.py build_base_docs This will generate an ``html/`` directory with the following structure:: @@ -443,13 +444,14 @@ files. This is useful when updating the general sections of the documentation, adding tutorials, modifying the build instructions, and more. -.. note:: In case you are interested in generating the Example Gallery, you - would need to first run ``python tools/example_gallery/main.py`` to - generate the examples ``rst`` for the gallery. +.. note:: In case you are interested in only generating the Example Gallery, + you would need to run ``python tools/example_gallery/main.py`` to + generate the examples ``documentation`` for the gallery. This will + also be used internally by the ``build_base_docs`` target -2. Building the documentation (rst + API) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +2. Building the documentation (Base + API) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The documentation is being generated using **qdoc** to get the API information, and also **sphinx** for the local Python related notes. @@ -474,9 +476,9 @@ to get the HTML files), you can go to the generated You can add ``-j X``, to perform the build process in parallel with X processes. -.. note:: The :command:`apidoc` make target builds offline documentation in ``QCH`` (Qt Creator Help) - format by default. You can switch to building for the online use with the ``--doc-build-online`` - configure option. +.. note:: The :command:`apidoc` make target builds offline documentation in ``QCH`` + (Qt Compressed Help) format by default. You can switch to building for the + online use with the ``--doc-build-online`` configure option. The target executes several steps: @@ -486,7 +488,7 @@ The target executes several steps: Re-running the command will not execute step 1 unless the file ``qdoc-output/webxml/qtcore-index.webxml`` is removed from the build tree. -Similarly, step 2 will not be executed unless the file ``rst/PySide6/QtCore/index.rst`` +Similarly, step 2 will not be executed unless the file ``base/PySide6/QtCore/index.rst`` is removed. Finally, you will get a ``html`` directory containing all the generated documentation. The offline @@ -496,18 +498,18 @@ can find ``Shiboken.qch`` in the build directory, ``build//build/ If you want to temporarily change a ``.rst`` file to examine the impact on formatting, you can re-run ``sphinx`` in the ``doc`` directory:: - sphinx-build rst html + sphinx-build base html Viewing offline documentation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The offline documentation (QCH) can be viewed using the Qt Creator IDE or Qt Assistant, which is -a standalone application for viewing QCH files. +The offline documentation (QCH) can be viewed using the *Qt Creator* IDE or +*Qt Assistant*, which is a standalone application for viewing QCH files. -To view the QCH using Qt Creator, following the instructions outlined in -`Using Qt Creator Help Mode `_. If you chose to -use Qt Assistant instead, use the following command to register the QCH file before launching -Qt Assistant:: +To view the QCH using *Qt Creator*, following the instructions outlined in +`Using Qt Creator Help Mode `_. +If you chose to use *Qt Assistant* instead, use the following command to register +the QCH file before launching *Qt Assistant*:: assistant -register PySide.qch @@ -519,7 +521,7 @@ Shiboken documentation. This can fail if * the default ``QCH`` format is used; in which case the required ``objects.inv`` files are not generated. Use ``--doc-build-online``. -* rst-only and full doc builds are mixed, resulting in wrong values for the +* base and full doc builds are mixed, resulting in wrong values for the intersphinx location in the CMake files. Re-run ``cmake`` to fix this. Using the internal tools diff --git a/sources/pyside6/doc/gettingstarted/package_details.rst b/sources/pyside6/doc/gettingstarted/package_details.rst index 17936251..113edb68 100644 --- a/sources/pyside6/doc/gettingstarted/package_details.rst +++ b/sources/pyside6/doc/gettingstarted/package_details.rst @@ -68,79 +68,8 @@ which is the original QtCore library. Adding the full URL because it's a different sphinx project. .. _`Shiboken Documentation`: https://doc.qt.io/qtforpython/shiboken6/gettingstarted.html -.. _package_tools: - Tools Included -------------- -Following the same idea from the modules, we also include in the packages -(wheels) Qt tools that are important for any Qt application development -workflow, like ``uic``, ``rcc``, etc. - -All the tools **must** be used from the PySide wrappers, and not directly. -For example, if exploring the ``site-packages/`` directory on your installation -you find ``uic.exe`` (on Windows), you should not click on that, and use -``pyside6-uic.exe`` instead. -The reason for this is the proper setup of PATHs, plugins, and more, -to properly work with the installed Python package. - -Here you can find all the tools we include in |project| starting -from 6.3.0, grouped by different topics: - -Project development -~~~~~~~~~~~~~~~~~~~ - -* ``pyside6-project``, to build Qt Designer forms (``.ui`` files), - resource files (``.qrc``) and QML type files (``.qmltype``) from - a ``.pyproject`` file. - -Widget Development -~~~~~~~~~~~~~~~~~~ - -* ``pyside6-designer``, drag-and-drop tool for designing Widget UIs (generates ``.ui`` files, - see :ref:`using_ui_files`). -* ``pyside6-uic``, to generate Python code from ``.ui`` form files. -* ``pyside6-rcc``, to generate serialized data from ``.qrc`` resources files. - Keep in mind these files can be used in other non-widget projects. - -QML Development -~~~~~~~~~~~~~~~ - -* ``pyside6-qmllint``, that verifies the syntactic validity of QML files. -* ``pyside6-qmltyperegistrar``, to read metatypes files and generate - files that contain the necessary code to register all the types marked with - relevant macros. -* ``pyside6-qmlimportscanner``, to identify the QML modules imported from a - project/QML files and dump the result as a JSON array. -* ``pyside6-qmlcachegen``, to compile QML to bytecode at compile time for bundling inside the - binary. - -Translations -~~~~~~~~~~~~ - -* ``pyside6-linguist``, for translating text in applications. -* ``pyside6-lrelease``, to create run-time translation files for the application. -* ``pyside6-lupdate``, to synchronize source code and translations. - -Qt Help -~~~~~~~ - -* ``pyside6-assistant``, for viewing online documentation in Qt Help file format. - Read more about the formats on the `QtHelp Framework`_ page. - -.. _`QtHelp Framework`: https://doc.qt.io/qt-6/qthelp-framework.html - -PySide Utilities -~~~~~~~~~~~~~~~~ - -* ``pyside6-genpyi``, to generate Python stubs (``.pyi`` files) for Qt modules. -* ``pyside6-metaobjectdump``, a tool to print out the metatype information in - JSON to be used as input for ``qmltyperegistrar``. - -Deployment -~~~~~~~~~~ - -* ``pyside6-deploy``, to deploy PySide6 applications to desktop platforms - - Linux, Windows and macOS. -* ``pyside6-android-deploy``, to deploy PySide6 application as an Android app - targeting different Android platforms - aarch64, armv7a, i686, x86_64. +PySide6 comes bundled with a set of tools that assist in making the development experience with +PySide6 more efficient. The list of tools can be found :ref:`here `. diff --git a/sources/pyside6/doc/gettingstarted/windows.rst b/sources/pyside6/doc/gettingstarted/windows.rst index c704b337..f688acd9 100644 --- a/sources/pyside6/doc/gettingstarted/windows.rst +++ b/sources/pyside6/doc/gettingstarted/windows.rst @@ -12,11 +12,8 @@ Requirements * ``sphinx`` package for the documentation (optional). * Check the platform dependencies of `Qt for Windows`_. -.. note:: Python 3.8.0 was missing some API required for PySide/Shiboken so it's not possible - to use it for a Windows build. - .. note:: The Python provided by the Microsoft Store is not compatible with PySide. Please - use https://python.org/download to get a Python Interpreter. + use https://www.python.org/downloads/ to get a Python Interpreter. .. _MSVC2022: https://visualstudio.microsoft.com/downloads/ .. _OpenSSL: https://sourceforge.net/projects/openssl/ diff --git a/sources/pyside6/doc/index.rst b/sources/pyside6/doc/index.rst index 83582835..1bb28f9c 100644 --- a/sources/pyside6/doc/index.rst +++ b/sources/pyside6/doc/index.rst @@ -99,7 +99,7 @@ Documentation PySide API reference. +++ - .. button-ref:: api + .. button-ref:: pyside-api :color: primary :outline: :expand: diff --git a/sources/pyside6/doc/inheritance_diagram.py b/sources/pyside6/doc/inheritance_diagram.py index d1e095ce..e972fac4 100644 --- a/sources/pyside6/doc/inheritance_diagram.py +++ b/sources/pyside6/doc/inheritance_diagram.py @@ -37,7 +37,6 @@ r""" :license: BSD, see LICENSE for details. """ -import sys try: from hashlib import md5 except ImportError: @@ -49,10 +48,7 @@ from docutils.parsers.rst import directives, Directive from sphinx.ext.graphviz import render_dot_html, render_dot_latex from inheritance_graph import InheritanceGraph -from import_inheritance import (get_inheritance_entries_by_import, - InheritanceException) -from json_inheritance import (is_inheritance_from_json_enabled, - get_inheritance_entries_from_json) +from import_inheritance import (InheritanceException) class inheritance_diagram(nodes.General, nodes.Element): @@ -112,27 +108,6 @@ def get_graph_hash(node): return md5(hashString.encode('utf-8')).hexdigest()[-10:] -def fix_class_name(name): - """Fix duplicated modules 'PySide6.QtCore.PySide6.QtCore.QObject'""" - mod_pos = name.rfind('.PySide') - return name[mod_pos + 1:] if mod_pos != -1 else name - - -def expand_ref_uri(uri): - """Fix a ref URI like 'QObject.html#PySide6.QtCore.PySide6.QtCore.QObject' - to point from the image directory back to the HTML directory.""" - anchor_pos = uri.find('#') - if anchor_pos == -1: - return uri - # Determine the path from the anchor "#PySide6.QtCore.PySide6.QtCore.QObject" - class_name = fix_class_name(uri[anchor_pos + 1:]) - path = '../' - modules = class_name.split('.') - for m in range(min(2, len(modules))): - path += f'{modules[m]}/' - return path + uri[:anchor_pos] # Strip anchor - - def html_visit_inheritance_diagram(self, node): """ Output the graph for HTML. This will insert a PNG with clickable @@ -149,7 +124,7 @@ def html_visit_inheritance_diagram(self, node): ref_title = child.get('reftitle') uri = child.get('refuri') if uri and ref_title: - urls[fix_class_name(ref_title)] = expand_ref_uri(uri) + urls[ref_title] = uri dotcode = graph.generate_dot(name, urls, env=self.builder.env) render_dot_html(self, node, dotcode, {}, 'inheritance', 'inheritance', diff --git a/sources/pyside6/doc/inheritance_graph.py b/sources/pyside6/doc/inheritance_graph.py index e45ebb17..00e0ac48 100644 --- a/sources/pyside6/doc/inheritance_graph.py +++ b/sources/pyside6/doc/inheritance_graph.py @@ -3,8 +3,7 @@ import sys -from import_inheritance import (get_inheritance_entries_by_import, - InheritanceException) +from import_inheritance import (get_inheritance_entries_by_import) from json_inheritance import (is_inheritance_from_json_enabled, get_inheritance_entries_from_json) diff --git a/sources/pyside6/doc/modules.rst b/sources/pyside6/doc/modules.rst index bffb49b7..8e77a461 100644 --- a/sources/pyside6/doc/modules.rst +++ b/sources/pyside6/doc/modules.rst @@ -44,7 +44,7 @@ Qt Modules Supported by Qt for Python .. grid-item-card:: :mod:`QtDesigner ` - Provides classes to extend Qt Designer. + Provides classes to extend *Qt Widgets Designer*. .. grid-item-card:: :mod:`QtGui ` @@ -114,6 +114,10 @@ Qt Modules Supported by Qt for Python Provides classes for setting up the controls from C++. + .. grid-item-card:: :mod:`QtQuickTest ` + + A unit test framework for QML applications where test cases are written as JavaScript functions. + .. grid-item-card:: :mod:`QtQuickWidgets ` Provides the QQuickWidget class for embedding Qt Quick in widget-based applications. @@ -169,7 +173,7 @@ Qt Modules Supported by Qt for Python .. grid-item-card:: :mod:`QtUiTools ` - Provides classes to handle forms created with Qt Designer. + Provides classes to handle forms created with *Qt Widgets Designer*. .. grid-item-card:: :mod:`Qt WebChannel ` diff --git a/sources/pyside6/doc/pysideinclude.py b/sources/pyside6/doc/pysideinclude.py index 66f8e31e..8e2834cc 100644 --- a/sources/pyside6/doc/pysideinclude.py +++ b/sources/pyside6/doc/pysideinclude.py @@ -11,7 +11,6 @@ from os import path from docutils import nodes from docutils.parsers.rst import Directive, directives -from sphinx import addnodes from sphinx.util import parselinenos @@ -69,7 +68,7 @@ class PySideInclude(Directive): codec_info = codecs.lookup(encoding) try: f = codecs.StreamReaderWriter(open(fn, 'Ub'), - codec_info[2], codec_info[3], 'strict') + codec_info[2], codec_info[3], 'strict') lines = f.readlines() f.close() except (IOError, OSError): @@ -103,10 +102,10 @@ class PySideInclude(Directive): lines = [lines[i] for i in linelist] startafter = self.options.get('start-after') - endbefore = self.options.get('end-before') - prepend = self.options.get('prepend') - append = self.options.get('append') - snippet = self.options.get('snippet') + endbefore = self.options.get('end-before') + prepend = self.options.get('prepend') + append = self.options.get('append') + snippet = self.options.get('snippet') if snippet: startafter = "//![%s]" % snippet diff --git a/sources/pyside6/doc/qdoc_spawner.py.in b/sources/pyside6/doc/qdoc_spawner.py.in index 0223e50e..d9d43436 100644 --- a/sources/pyside6/doc/qdoc_spawner.py.in +++ b/sources/pyside6/doc/qdoc_spawner.py.in @@ -80,6 +80,12 @@ if __name__ == "__main__": parser.add_argument("--verbose", dest="verbose", action="store_true", default=False) args = parser.parse_args() + + core_index = Path(args.doc_data_dir) / "webxml" / "qtcore-index.webxml" + if core_index.is_file(): + print(f"qdoc_spawner: {core_index} already exists, bailing out") + sys.exit(0) + files_prepare, files_single_exec = get_qdocconf_files() parallel = args.parallel diff --git a/sources/pyside6/doc/qtattributionsscannertorst.py b/sources/pyside6/doc/qtattributionsscannertorst.py index aae4bccf..677371c4 100644 --- a/sources/pyside6/doc/qtattributionsscannertorst.py +++ b/sources/pyside6/doc/qtattributionsscannertorst.py @@ -8,7 +8,6 @@ Tool to run qtattributionsscanner and convert its output to rst import os import json import subprocess -import sys import warnings from argparse import ArgumentParser, RawTextHelpFormatter from pathlib import Path @@ -22,8 +21,8 @@ libexec_dir = None def indent(lines, indent): result = '' - for l in lines: - result = f"{result}{indent}{l}\n" + for line in lines: + result = f"{result}{indent}{line}\n" return result @@ -96,7 +95,7 @@ def runScanner(directory, targetFileName, libexec_dir): url = entry['Homepage'] version = entry['Version'] if url and version: - content = f"{content}{rstUrl('Project Homepage', url)}, upstream version: {version}\n\n" + content = f"{content}{rstUrl('Project Homepage', url)}, upstream version: {version}\n\n" # noqa E:501 copyright = entry['Copyright'] if copyright: content += rstLiteralBlockFromText(copyright) diff --git a/sources/pyside6/doc/qtmodules/pyside-qtquicktest.qdocconf.in b/sources/pyside6/doc/qtmodules/pyside-qtquicktest.qdocconf.in new file mode 100644 index 00000000..1d839753 --- /dev/null +++ b/sources/pyside6/doc/qtmodules/pyside-qtquicktest.qdocconf.in @@ -0,0 +1,3 @@ +include(@QT_SRC_DIR@/../qtdeclarative/src/qmltest/doc/qtqmltest.qdocconf) +includepaths += -I @QT_SRC_DIR@/../qtdeclarative/src/qmltest +include(../pyside-config.qdocconf) diff --git a/sources/pyside6/doc/tools/index.rst b/sources/pyside6/doc/tools/index.rst new file mode 100644 index 00000000..b421a428 --- /dev/null +++ b/sources/pyside6/doc/tools/index.rst @@ -0,0 +1,211 @@ +.. _package_tools: + +Tools +===== + +Following the same idea from the modules, we also include in the packages +(wheels) Qt tools that are important for any Qt application development +workflow, like ``uic``, ``rcc``, etc. + +All the tools **must** be used from the PySide wrappers, and not directly. +For example, if exploring the ``site-packages/`` directory on your installation +you find ``uic.exe`` (on Windows), you should not click on that, and use +``pyside6-uic.exe`` instead. +The reason for this is the proper setup of PATHs, plugins, and more, +to properly work with the installed Python package. + +Here you can find all the tools we include in |project| starting +from 6.3.0, grouped by different topics: + +Project development +~~~~~~~~~~~~~~~~~~~ + +.. grid:: 2 + :gutter: 3 3 4 5 + + .. grid-item-card:: ``pyside6-project`` + :link: pyside6-project + :link-type: ref + + to build *Qt Widgets Designer* forms (``.ui`` files), + resource files (``.qrc``) and QML type files (``.qmltype``) + from a ``.pyproject`` file. + +Widget Development +~~~~~~~~~~~~~~~~~~ + +.. grid:: 2 + :gutter: 3 3 4 5 + + .. grid-item-card:: ``pyside6-designer`` + :link: pyside6-designer + :link-type: ref + + drag-and-drop tool for designing Widget UIs (generates ``.ui`` files, + see :ref:`using_ui_files`). + + .. grid-item-card:: ``pyside6-uic`` + :link: pyside6-uic + :link-type: ref + + to generate Python code from ``.ui`` form files. + + .. grid-item-card:: ``pyside6-rcc`` + :link: pyside6-rcc + :link-type: ref + + to generate serialized data from ``.qrc`` resources files. + Keep in mind these files can be used in other non-widget projects. + + +QML Development +~~~~~~~~~~~~~~~ + +.. grid:: 2 + :gutter: 3 3 4 5 + + .. grid-item-card:: ``pyside6-qmllint`` + :link: pyside6-qmllint + :link-type: ref + + that verifies the syntactic validity of QML files. + + .. grid-item-card:: ``pyside6-qmltyperegistrar`` + :link: pyside6-qmltyperegistrar + :link-type: ref + + to read metatypes files and generate files that contain the necessary + code to register all the types marked with relevant macros. + + .. grid-item-card:: ``pyside6-qmlimportscanner`` + :link: pyside6-qmlimportscanner + :link-type: ref + + to identify the QML modules imported from a + project/QML files and dump the result as a JSON array. + + .. grid-item-card:: ``pyside6-qmlcachegen`` + :link: pyside6-qmlcachegen + :link-type: ref + + to compile QML to bytecode at compile time for bundling inside the + binary. + + .. grid-item-card:: ``pyside6-qml`` + :link: pyside6-qml + :link-type: ref + + to enable quick prototyping with QML files. This tool mimics some of + the capabilities of Qt's ``QML`` runtime utility by + directly invoking QQmlEngine/QQuickView. + +Translations +~~~~~~~~~~~~ + +.. grid:: 2 + :gutter: 3 3 4 5 + + .. grid-item-card:: ``pyside6-linguist`` + :link: pyside6-linguist + :link-type: ref + + for translating text in applications. + + .. grid-item-card:: ``pyside6-lrelease`` + :link: pyside6-lrelease + :link-type: ref + + to create run-time translation files for the application. + + .. grid-item-card:: ``pyside6-lupdate`` + :link: pyside6-lupdate + :link-type: ref + + to synchronize source code and translations. + +Qt Help +~~~~~~~ + +.. grid:: 2 + :gutter: 3 3 4 5 + + .. grid-item-card:: ``pyside6-assistant`` + :link: pyside6-assistant + :link-type: ref + + for viewing online documentation in Qt Help file format. + Read more about the formats on the `QtHelp Framework`_ page. + +.. _`QtHelp Framework`: https://doc.qt.io/qt-6/qthelp-framework.html + +PySide Utilities +~~~~~~~~~~~~~~~~ + +.. grid:: 2 + :gutter: 3 3 4 5 + + .. grid-item-card:: ``pyside6-genpyi`` + :link: pyside6-genpyi + :link-type: ref + + to generate Python stubs (``.pyi`` files) for Qt modules. + + .. grid-item-card:: ``pyside6-metaobjectdump`` + :link: pyside6-metaobjectdump + :link-type: ref + + a tool to print out the metatype information in JSON to be used as + input for ``qmltyperegistrar``. + +Deployment +~~~~~~~~~~ + +.. grid:: 2 + :gutter: 3 3 4 5 + + .. grid-item-card:: ``pyside6-deploy`` + :link: pyside6-deploy + :link-type: ref + + to deploy PySide6 applications to desktop platforms - Linux, Windows + and macOS. + + .. grid-item-card:: ``pyside6-android-deploy`` + :link: pyside6-android-deploy + :link-type: ref + + to deploy PySide6 application as an Android app targeting different + Android platforms - aarch64, armv7a, i686, x86_64. + +Shader Tools +~~~~~~~~~~~~ + +.. grid:: 2 + :gutter: 3 3 4 5 + + .. grid-item-card:: ``pyside6-qsb`` + :link: pyside6-qsb + :link-type: ref + + a command-line tool provided by the Qt Shader Tools modules to + generate and inspect .qsb files. + +Qt Quick 3D +~~~~~~~~~~~ + +.. grid:: 2 + :gutter: 3 3 4 5 + + .. grid-item-card:: ``pyside6-balsam`` + :link: pyside6-balsam + :link-type: ref + + a command line tool that takes assets created in digital content + creation tools like Maya, 3ds Max or Blender and converts them into an + efficient runtime format for use with Qt Quick 3D. + + .. grid-item-card:: ``pyside6-balsamui`` + :link: pyside6-balsamui + :link-type: ref + + a graphical user interface for the ``pyside6-balsam`` tool. diff --git a/sources/pyside6/doc/tools/pyside-assistant.rst b/sources/pyside6/doc/tools/pyside-assistant.rst new file mode 100644 index 00000000..c9b3470c --- /dev/null +++ b/sources/pyside6/doc/tools/pyside-assistant.rst @@ -0,0 +1,21 @@ +.. _pyside6-assistant: + +pyside6-assistant +================= + +``pyside6-assistant`` is a tool that wraps `Qt Assistant`_, the help +viewer of Qt for use with the Qt help file format (see `The Qt Help Framework`_). + +The version of assistant shipped with Qt for Python does not contain +any documentation. + +You can build the Qt for Python documentation in the Qt help file format +(see :ref:`building_documentation`) and register it for use in `Preferences` +dialog of ``pyside6-assistant`` (`Edit/Preferences`). + +.. image:: pyside6-assistant_screenshot.webp + :width: 381 + :alt: PySide6 Assistant Screenshot + +.. _`Qt Assistant`: https://doc.qt.io/qt-6/qtassistant-index.html +.. _`The Qt Help Framework`: https://doc.qt.io/qt-6/qthelp-framework.html diff --git a/sources/pyside6/doc/tools/pyside-designer.rst b/sources/pyside6/doc/tools/pyside-designer.rst new file mode 100644 index 00000000..4cde6b2b --- /dev/null +++ b/sources/pyside6/doc/tools/pyside-designer.rst @@ -0,0 +1,91 @@ +.. _pyside6-designer: + +pyside6-designer +================ + +``pyside6-designer`` is a tool that wraps the `Qt Widgets Designer`_, +to enable you to design Qt Widgets applications with a *drag-and-drop* +approach. + +.. image:: pyside6-designer_screenshot.webp + :width: 100% + :alt: PySide6 Designer Screenshot + +Usage +----- + +With ``pyside6-designer`` you can design your application in a simple way, +to later save the end result in a ``.ui`` file. When you start the tool, you +will see a dialog to select the base window: a QWidget, a QMainWindow, etc. + +.. image:: pyside6-designer_base_screenshot.webp + :width: 50% + :alt: PySide6 Designer Initial Screenshot + +Once you select one of those options, you can start placing widgets +into the interface, and have access to the whole structure, properties, +and more. + +.. image:: pyside6-designer_sections_screenshot.webp + :width: 100% + :alt: PySide6 Designer Initial Screenshot + +A simple distinction of the areas you might use is described in the +previous screenshot. In the section **1** you will find all the elements +you can use in your application, which is the area **2**. +The application designs follows a hierarchical configuration, +in **3** you can see the structure of the example which contains +only a ``QMainWindow`` and a ``QPushButton``. Lastly +you can access and modify the properties of the item in **4**, +where you could adjust dimensions, names, etc. + + +When your application is finished, you will save your design in a ``.ui`` +file. This ``.ui`` file can later be converted into a Python file, +with the help of the :ref:`pyside6-uic` tool. You can find +more details of this process in: :ref:`using_ui_files`. + +If you are interested in more functionality of the tool, you can check +the official `Qt Widgets Designer Manual`_. + + +Custom Widgets +-------------- + +One of the features that the `Qt Widgets Designer`_ provides is the possibility +of loading custom widgets, in order to facilitate the development with ad-hoc +widgets. + +On the following screenshot, you can see a new component on the left column +that is already added on the main widget, a tic-tac-toe custom widget. + +.. image:: pyside6-designer_customwidgets_screenshot.webp + :width: 100% + :alt: PySide6 Designer Custom Widgets Screenshot + +To achieve this, you need to register a custom widget by setting the environment +variable ``PYSIDE_DESIGNER_PLUGINS`` to the directory where your register file +is located. The registration file for the tic-tac-toe widget looks like this: + +.. code-block:: Python + + from tictactoe import TicTacToe + from tictactoeplugin import TicTacToePlugin + + from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection + + + if __name__ == '__main__': + QPyDesignerCustomWidgetCollection.addCustomWidget(TicTacToePlugin()) + +As you can see in the previous code, ``TicTacToe`` is the custom widget, +imported from a different file, and the ``TicTacToePlugin`` is the interface +layer for `Qt Widgets Designer`_ to recognize it. +We provide a helper class, ``QPyDesignerCustomWidgetCollection``, to +simplify the registration process. + +If you are interested in all the details of this specific case, you can +check the :ref:`task-menu-extension-example`. + +.. _`Qt Widgets Designer`: https://doc.qt.io/qt-6/qtdesigner-manual.html +.. _`Qt Widgets Designer Manual`: https://doc.qt.io/qt-6/qtdesigner-manual.html diff --git a/sources/pyside6/doc/tools/pyside-genpyi.rst b/sources/pyside6/doc/tools/pyside-genpyi.rst new file mode 100644 index 00000000..0240c500 --- /dev/null +++ b/sources/pyside6/doc/tools/pyside-genpyi.rst @@ -0,0 +1,52 @@ +.. _pyside6-genpyi: + +pyside6-genpyi +============== + +`pyside6-genpyi` is a command line tool to generate Python stub files +(.pyi) for PySide modules. Stub files define signatures of all classes, +methods (including overloads), constants and enums of the PySide +modules. Signatures also contain type hints. This helps PySide integrate +with Python type checkers and IDEs. For example, if you use any function +from the Qt API with PySide, your IDE's function lookup feature will +show you the function signature and its parameters and return value +including types. + +PySide6 already ships with stub files that were generated with +`pyside6-genpyi`. However, if you want to generate new stub files for +several (or all) modules, for example to toggle a few features, you can +run `pyside6-genpyi` manually. If you want to generate stub files for +your own custom module, refer to :ref:`shiboken6-genpyi`. + + +Usage +----- + +To generate stub files for a PySide module, run the following command: + +.. code-block:: bash + + pyside6-genpyi [OPTIONS] + +where `` is a space-separated list of module names (the +modules must be importable from the working directory) and where +`[OPTIONS]` can be one of the following: + +* **--quiet**: Run the tool quietly without output to stdout. +* **--outpath **: Specify the output directory for the + generated stub files. If not specified, the stub files are generated + in the location of the module binary. +* **--sys-path **: Prepend the system path (`sys.path`) with a + space-separated list of strings ``. This is useful if the + module is not installed in a default lookup location. +* **--feature **: A space-separate list of optional PySide + features to enable (see :ref:`pysideapi2`). This option has no effect + when using PyPy. Currently, the following features are available: + + * **snake_case**: All methods in the module are switched from + ``camelCase`` to ``snake_case``. A single upper case letter is + replaced by an underscore and the lower case letter. + * **true_property**: All getter and setter functions in the module + which are marked as a property in the Qt6 docs are replaced by Python + property objects. Properties are also listed as such in the according + QMetaObject of a class. diff --git a/sources/pyside6/doc/tools/pyside-linguist.rst b/sources/pyside6/doc/tools/pyside-linguist.rst new file mode 100644 index 00000000..e13124a8 --- /dev/null +++ b/sources/pyside6/doc/tools/pyside-linguist.rst @@ -0,0 +1,20 @@ +.. _pyside6-linguist: + +pyside6-linguist +================= + +``pyside6-linguist`` is a tool that wraps `Qt Linguist`_, Qt's tool to +translate user interfaces and manage application localizations. It +supports Qt's own TS file format as well as the XML Localization +Interchange File Format (XLIFF). There are no differences between the +version bundled with PySide and the one from Qt. + +For more information on how to use this tool, read Qt's documentation +here: `Qt Linguist`_. Read more about how to translate PySide +applications here: :ref:`translations`. + +.. image:: pyside6-linguist_screenshot.webp + :width: 500 + :alt: PySide6 Linguist Screenshot + +.. _`Qt Linguist`: https://doc.qt.io/qt-6/qtlinguist-index.html diff --git a/sources/pyside6/doc/tools/pyside-lrelease.rst b/sources/pyside6/doc/tools/pyside-lrelease.rst new file mode 100644 index 00000000..7c628f2a --- /dev/null +++ b/sources/pyside6/doc/tools/pyside-lrelease.rst @@ -0,0 +1,25 @@ +.. _pyside6-lrelease: + +pyside6-lrelease +================ + +.. note:: This tool is automatically called by :ref:`pyside6-project` + so you don't need to call it manually. *Qt Creator* will take care + of this step as well while executing a project. + +``pyside6-lrelease`` is a command line tool wrapping `lrelease`_. It produces +``.qm`` files out of ``.ts`` files. The ``.qm`` file format is a compact binary +format that the localized application uses. It provides extremely fast lookup +for translations (see :ref:`translations`). + +Usage +----- + +To convert a ``.ts`` file of the :ref:`qt-linguist-example` +into its binary representation, run: + +.. code-block:: bash + + pyside6-lrelease example_de.ts -qm example_de.qm + +.. _`lrelease`: https://doc.qt.io/qt-6/linguist-lrelease.html diff --git a/sources/pyside6/doc/tools/pyside-lupdate.rst b/sources/pyside6/doc/tools/pyside-lupdate.rst new file mode 100644 index 00000000..1c0d73c1 --- /dev/null +++ b/sources/pyside6/doc/tools/pyside-lupdate.rst @@ -0,0 +1,23 @@ +.. _pyside6-lupdate: + +pyside6-lupdate +=============== + +.. note:: This tool is automatically called by :ref:`pyside6-project` + so you don't need to call it manually. + +``pyside6-lupdate`` is a command line tool wrapping `lupdate`_. It finds +translatable strings in Python, ``.ui``, and ``.qml`` files and generates or +updates ``.ts`` files (see :ref:`translations`). + +Usage +----- + +To create or update the ``.ts`` file of the :ref:`qt-linguist-example`, +run: + +.. code-block:: bash + + pyside6-lupdate main.py main.qml form.ui -ts example_de.ts + +.. _`lupdate`: https://doc.qt.io/qt-6/linguist-lupdate.html diff --git a/sources/pyside6/doc/tools/pyside-metaobjectdump.rst b/sources/pyside6/doc/tools/pyside-metaobjectdump.rst new file mode 100644 index 00000000..1522b7ad --- /dev/null +++ b/sources/pyside6/doc/tools/pyside-metaobjectdump.rst @@ -0,0 +1,92 @@ +.. _pyside6-metaobjectdump: + +pyside6-metaobjectdump +====================== + +``pyside6-metaobjectdump`` is a command line tool. It scans Python source +files and dumps out information on classes to be registered with QML in +JSON-format. This serves as input for the :ref:`pyside6-qmltyperegistrar` tool. + +The tool is the equivalent of the `moc`_ tool in Qt / C++. + +It is automatically run by the :ref:`pyside6-project` tool +when passing the ``qmllint`` argument instructing it to check +the QML source files. + +Usage +----- + +Classes to be registered with QML are indicated by QML decorators +like :deco:`QmlElement`. Invoking: + +.. code-block:: bash + + pyside6-metaobjectdump birthdayparty.py + +produces the JSON data on stdout: + +.. code-block:: json + + [ + { + "classes": [ + { + "className": "BirthdayParty", + "qualifiedClassName": "BirthdayParty", + "object": true, + "superClasses": [ + { + "access": "public", + "name": "QObject" + } + ], + "classInfos": [ + { + "name": "QML.Element", + "value": "auto" + } + ], + "properties": [ + { + "name": "host", + "type": "Person", + "index": 0, + "read": "host", + "notify": "host_changed", + "write": "host" + }, + { + "name": "guests", + "type": "QQmlListProperty", + "index": 1 + } + ], + "signals": [ + { + "access": "public", + "name": "host_changed", + "arguments": [], + "returnType": "void" + }, + { + "access": "public", + "name": "guests_changed", + "arguments": [], + "returnType": "void" + } + ] + } + ], + "outputRevision": 68, + "QML_IMPORT_NAME": "People", + "QML_IMPORT_MAJOR_VERSION": 1, + "QML_IMPORT_MINOR_VERSION": 0, + "QT_MODULES": [ + "QtCore", + "QtQml" + ], + "inputFile": ".../examples/qml/tutorials/extending-qml-advanced/advanced1-Base-project/birthdayparty.py" + } + ] + +.. _`moc`: https://doc.qt.io/qt-6/moc.html diff --git a/sources/pyside6/doc/tools/pyside-project.rst b/sources/pyside6/doc/tools/pyside-project.rst new file mode 100644 index 00000000..0359e1b7 --- /dev/null +++ b/sources/pyside6/doc/tools/pyside-project.rst @@ -0,0 +1,64 @@ +.. _pyside6-project: + +pyside6-project +=============== + +`pyside6-project` is a command line tool for creating, building and deploying +|project| applications. It operates on a project file which is also used by +`Qt Creator`_. + +Project file format +------------------- + +The project file format is a simple `JSON`_-based format with the suffix +``.pyproject`` listing all files of the project excluding generated files +(typically ``.py``, ``.qml``, ``.qrc``, ``.ts``, or ``.ui`` files): + +.. code-block:: json + + { + "files": ["main.py"] + } + + +Usage +----- + +The tool has several subcommands. New projects can be created using +the below commands, passing the project name (directory): + +*new-ui* + Creates a new QtWidgets project with a *Qt Widgets Designer*-based main + window. + +*new-widget* + Creates a new QtWidgets project with a main window. + +*new-quick* + Creates a new QtQuick project. + +The other commands take the project file as an argument. +It is also possible to specify a directory containing the project file. + +*build* + Builds the project, generating the required build artifacts + (see :ref:`using_ui_files`, :ref:`using_qrc_files`). + +*run* + Builds the project and runs the main. + +*deploy* + Deploys the application (see see :ref:`pyside6-deploy`). + +*lupdate* + Updates translation (.ts) files (see :ref:`translations`). + +*clean* + Cleans the build artifacts. + +*qmllint* + Runs the ``qmllint`` tool, checking the QML files. + + +.. _`Qt Creator`: https://www.qt.io/product/development-tools +.. _`JSON`: https://www.json.org/ diff --git a/sources/pyside6/doc/tools/pyside-qml.rst b/sources/pyside6/doc/tools/pyside-qml.rst new file mode 100644 index 00000000..0502dd94 --- /dev/null +++ b/sources/pyside6/doc/tools/pyside-qml.rst @@ -0,0 +1,84 @@ +.. _pyside6-qml: + +pyside6-qml +=========== + +``pyside6-qml`` mimics some capabilities of Qt's `qml `_ runtime utility by directly +invoking QQmlEngine/QQuickView. It enables prototyping with QML/QtQuick without the need to write +any Python code that loads the QML files either through `QQmlApplicationEngine `_ or +the `QQuickView `_ class. The tool also detects the QML classes implemented in Python +and registers them with the QML type system. + +Usage +----- + +Consider the example `Extending QML - Plugins Example `_. This example does +not have a Python file with a ``main`` function that initializes a QmlEngine to load the QML file +``app.qml``. You can run the example by running + +.. code-block:: bash + + pyside6-qml examples/qml/tutorials/extending-qml/chapter6-plugins/app.qml -I examples/qml/tutorials/extending-qml/chapter6-plugins/Charts + +The ``-I`` flag is used to point ``pyside6-qml`` to the folder containing Python files that +implement QML classes. + +Command Line Options +-------------------- + +Here are all the command line options of ``pyside6-qml``: + +Arguments +^^^^^^^^^ + +* **file**: This option refers to the QML file to be loaded by ``pyside6-qml``. This option does not + have a name or a flag. Therefore, this option should be the first option supplied to + ``pyside6-qml``. For example, + +.. code-block:: bash + + pyside6-qml /path/to/test.qml + +Options +^^^^^^^ + +* **--module-paths/-I**: Specify space-separated folder/file paths which point to the Python files + that implement QML classes. By default, the parent directory of the QML file supplied to + ``pyside6-qml`` is searched recursively for all Python files and they are imported. Otherwise, + only the paths given in module paths are searched. + +* **--verbose/-v**: Run ``pyside6-qml`` in verbose mode. When run in this mode, pyside6-qml prints + log messages during various stages of processing. + +Options that align with `QML `_ runtime utility +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* **--app-typ/-a**: Specifies which application class to use. It takes one of the three values - + ``core, gui, widget``. The default value is *gui*. + +* **--config/-c**: Load the given built-in configuration. It takes one of two values - ``default, + resizeToItem``. This option is only relevant for a QtQuick application. If ``default`` is used, + the view resizes to the size of the root item in the QML. If ``resizeToItem`` is used, the view + automatically resizes the root item to the size of the view. + +* **--list-conf**: List the built-in configurations. ``pyside6-qml`` has two built-in configurations + - ``default`` and ``resizeToItem``. See the option ``--config`` for more information. + +* **--rhi/-r**: Specifies the backend for the Qt graphics abstraction (RHI). It takes one of the + four values - ``vulkan, metal, d3dll, gl``. + +* **--verbose/-v**: List the built-in configurations. ``pyside6-qml`` has two built-in + configurations - *default* and *resizeToItem*. See the option ``--config`` for more information. + +* **--gles**: Force use of GLES (AA_UseOpenGLES). + +* **--desktop**: Force use of desktop OpenGL (AA_UseDesktopOpenGL). + +* **--software**: Force use of software rendering(AA_UseSoftwareOpenGL). + +* **--disable-context-sharing**: Disable the use of a shared GL context for QtQuick Windows". + +.. _`qml_runtime`: https://doc.qt.io/qt-6/qtquick-qml-runtime.html +.. _`qqmlappengine`: https://doc.qt.io/qt-6/qqmlapplicationengine.html +.. _`qquickview`: https://doc.qt.io/qt-6/qquickview.html +.. _`extending_qml_example`: https://doc.qt.io/qtforpython-6/examples/example_qml_tutorials_extending-qml_chapter6-plugins.html diff --git a/sources/pyside6/doc/tools/pyside-qmlcachegen.rst b/sources/pyside6/doc/tools/pyside-qmlcachegen.rst new file mode 100644 index 00000000..ecd8e6af --- /dev/null +++ b/sources/pyside6/doc/tools/pyside-qmlcachegen.rst @@ -0,0 +1,25 @@ +.. _pyside6-qmlcachegen: + +pyside6-qmlcachegen +=================== + +``pyside6-qmlcachegen`` is a command line tool that wraps `qmlcachegen`_. +This tool creates C++ code or `QML byte code` for ``.qml`` files. For +Qt for Python, only `QML byte code` is relevant. The file suffix is +``.qmlc`` and it works similar to compiled Python bytecode +(``.pyc`` files). + +Usage +----- + +The command line option ``--only-bytecode`` should be used to +create `QML byte code`. For example: + +.. code-block:: bash + + qmlcachegen --only-bytecode gallery.qml + +produces a file ``gallery.qmlc`` containing `QML byte code` which is +automatically loaded by the QML engine. + +.. _`qmlcachegen`: https://doc.qt.io/qt-6/qtqml-tool-qmlcachegen.html diff --git a/sources/pyside6/doc/tools/pyside-qmllint.rst b/sources/pyside6/doc/tools/pyside-qmllint.rst new file mode 100644 index 00000000..54a72470 --- /dev/null +++ b/sources/pyside6/doc/tools/pyside-qmllint.rst @@ -0,0 +1,49 @@ +.. _pyside6-qmllint: + +pyside6-qmllint +=============== + +``pyside6-qmllint`` is a command line tool that wraps `qmllint`_. This tool +verifies the syntatic validity of QML files and warns about some QML +anti-patterns. + +It is automatically run by the :ref:`pyside6-project` tool +when passing the ``qmllint`` argument instructing it to check +the QML source files. + +Usage +----- + +The tool should normally not be invoked manually since it requires +a number of import paths and additional type information +generated by :ref:`pyside6-qmltyperegistrar` to function. + +For example, for a ``.qml`` file like: + +.. code-block:: javascript + + import QtQuick + import QtQuick.Controls + + Item { + Text { + id: name + text: qsTr("Hello World") + } + } + +when running: + +.. code-block:: bash + + pyside6-qmllint Main.qml + +it would warn about unused imports: + +.. code-block:: + + Info: Main.qml:2:1: Unused import [unused-imports] + import QtQuick.Controls + ^^^^^^ + +.. _`qmllint`: https://doc.qt.io/qt-6/qtquick-tool-qmllint.html diff --git a/sources/pyside6/doc/tools/pyside-qmltyperegistrar.rst b/sources/pyside6/doc/tools/pyside-qmltyperegistrar.rst new file mode 100644 index 00000000..0e8d66bd --- /dev/null +++ b/sources/pyside6/doc/tools/pyside-qmltyperegistrar.rst @@ -0,0 +1,17 @@ +.. _pyside6-qmltyperegistrar: + +pyside6-qmltyperegistrar +======================== + +``pyside6-qmltyperegistrar`` is a command line tool that wraps the +``qmltyperegistrar`` tool of Qt. + +It takes the file produced by :ref:`pyside6-metaobjectdump` +as input and generates C++ code to register C++ classes to QML +and a ``.qmltypes`` file containing a JSON description of the +classes. For Qt for Python, only the ``.qmltypes`` file +is of interest as input for :ref:`pyside6-qmllint`. + +The tool is automatically run by the :ref:`pyside6-project` tool +when passing the ``qmllint`` argument instructing it to check +the QML source files. diff --git a/sources/pyside6/doc/tools/pyside-rcc.rst b/sources/pyside6/doc/tools/pyside-rcc.rst new file mode 100644 index 00000000..e5688485 --- /dev/null +++ b/sources/pyside6/doc/tools/pyside-rcc.rst @@ -0,0 +1,55 @@ +.. _pyside6-rcc: + +pyside6-rcc +=========== + +.. note:: This tool is automatically called by :ref:`pyside6-project` + so you don't need to call it manually. *Qt Creator* will take care + of this step as well while executing a project. + + +``pyside6-rcc`` is a command line tool for converting ``.qrc`` files into ``.py`` +files, so they can be used within your Python code. + +The tool is a wrapper around the `rcc`_ tool, which was originally +designed to generate C++ code, but it also has Python support. + +Even though the equivalent of ``pyside6-rcc`` is running ``rcc -g python`` +we strongly recommend you to rely on ``pyside6-rcc`` in order to avoid +mismatches between versions for the generated code. + +Usage +----- + +Once you have gathered your resources on a qrc file, +you can transform your ``.qrc`` file with the following command: + +.. code-block:: bash + + pyside6-rcc your_file.qrc -o rc_your_file.py + +It is important to use the ``-o`` option to generate the Python file with the +conversion, otherwise you will receive all the output as stdout in your terminal. + +To enable the usage of those resources in your program, you need to import +the file: + +.. code-block:: Python + + import rc_your_file + +then you can use a specific resource, for example an image, with the prefix ``:/``, +for example: + +.. code-block:: Python + + pixmap = QPixmap(":/icons/image.png") + + +For additional options, you can use ``pyside6-rcc -h`` in order to get +more information about additional options. + +Visit the tutorial :ref:`using_qrc_files` for a hands-on example. + +.. _`rcc`: https://doc.qt.io/qt-6/rcc.html + diff --git a/sources/pyside6/doc/tools/pyside-uic.rst b/sources/pyside6/doc/tools/pyside-uic.rst new file mode 100644 index 00000000..ba4e36b4 --- /dev/null +++ b/sources/pyside6/doc/tools/pyside-uic.rst @@ -0,0 +1,59 @@ +.. _pyside6-uic: + +pyside6-uic +=========== + +.. note:: This tool is automatically called by :ref:`pyside6-project` + so you don't need to call it manually. *Qt Creator* will take care + of this step as well while executing a project. + +``pyside6-uic`` is a command line tool for converting ``.ui`` files into ``.py`` +files, with the objective of using application designs as Python classes. + +The tool is a wrapper around the `uic`_ tool, which was originally +designed to generate C++ code, but it also has Python support. + +Even though the equivalent of ``pyside6-uic`` is running ``uic -g python`` +we strongly recommend you to rely on ``pyside6-uic`` in order to avoid +mismatches between versions for the generated code. + +Usage +----- + +Once you have designed your application with :ref:`pyside6-designer`, +you can transform your ``.ui`` file with the following command: + +.. code-block:: bash + + pyside6-uic your_file.ui -o ui_your_file.py + +It is important to use the ``-o`` option to generate the Python file with the +conversion, otherwise you will receive all the output as stdout in your terminal. + +The structure of the generated Python file will be similar in all cases, +and you will get one class called ``Ui_TheNameOfYourDesign(object)`` that +is in charge of positioning all the elements like your design. + +To use this Python file, you should follow our tutorial in +:ref:`using_ui_files`, but in summary, it is mainly importing the class +from the generated file and setting it up in your code: + +.. code-block:: Python + + self.ui = Ui_TheNameOfYourDesign() + self.ui.setupUi(self) + +For additional options, you can use ``pyside-uic -h`` in order to get +more information related to relative imports, absolute imports, using resources, +translations, etc. + +.. note:: Remember that you need to have a class corresponding to the base + form you selected in :ref:`pyside6-designer`, a ``QWidget``, or ``QDialog``, + or ``QMainWindow``, etc, in order for ``setupUi`` to work. Check + :ref:`using_ui_files` for more information. + +.. warning:: Do not modify the content of the generated Python file from your + ``.ui`` file, otherwise everything will be lost when you re-generate it. + +.. _`uic`: https://doc.qt.io/qt-6/uic.html + diff --git a/sources/pyside6/doc/tools/pyside6-assistant_screenshot.webp b/sources/pyside6/doc/tools/pyside6-assistant_screenshot.webp new file mode 100644 index 00000000..7d898d8e Binary files /dev/null and b/sources/pyside6/doc/tools/pyside6-assistant_screenshot.webp differ diff --git a/sources/pyside6/doc/tools/pyside6-balsam.rst b/sources/pyside6/doc/tools/pyside6-balsam.rst new file mode 100644 index 00000000..c6677f6a --- /dev/null +++ b/sources/pyside6/doc/tools/pyside6-balsam.rst @@ -0,0 +1,59 @@ +.. _pyside6-balsam: + +pyside6-balsam +============== + +``pyside6-qsb`` is a tool that wraps the `balsam `_ +tool provided with Qt Quick 3D. The Balsam tool is a command line application +that is part of Qt Quick 3D's asset conditioning pipeline. The purpose is to +take assets created in digital content creation tools like `Maya`_, `3ds Max`_ +or `Blender`_ and converts them into an efficient runtime format for use with Qt +Quick 3D. It is not possible, nor does it make sense to reference the +interchange formats directly in applications because a large amount of +resources are needed to parse and condition the content of the asset before it +is usable for real-time rendering. Instead, the interchange formats can be +converted via the Balsam tool into QML Components and resources like geometry +and textures. + + +For more information on how to use this tool, read Qt's documentation +here: `Balsam Asset Import Tool`_. + +Usage +----- + +.. code-block:: bash + + pyside6-balsam [options] sourceFileName + +To convert a 3D asset contained in the file ``testModel.fbx`` with +``pyside6-balsam`` the following command would be used: + +.. code-block:: bash + + pyside6-balsam testModel.fbx + +This would generate the following files: + +* meshes/testModel.mesh +* TestModel.qml + +Which can then be used in a Qt Quick 3D project by using that QML Component: + +.. code-block:: xml + + import QtQuick3D 1.0 + + Scene { + Model { + source: "TestModel.qml" + } + } + +For other modes of operation, refer to the `Balsam Asset Import Tool`_. + +.. _`Balsam Asset Import Tool`: https://doc.qt.io/qt-6/qtquick3d-tool-balsam.html +.. _Maya: https://www.autodesk.com/products/maya/overview +.. _3ds Max: https://www.autodesk.com/products/3ds-max/overview +.. _Blender: https://www.blender.org/ + diff --git a/sources/pyside6/doc/tools/pyside6-balsamui.rst b/sources/pyside6/doc/tools/pyside6-balsamui.rst new file mode 100644 index 00000000..f34cb604 --- /dev/null +++ b/sources/pyside6/doc/tools/pyside6-balsamui.rst @@ -0,0 +1,22 @@ +.. _pyside6-balsamui: + +pyside6-balsamui +================ + +``pyside6-balsamui`` is graphical user interface frontend to the command line +tool :ref:`pyside6-balsam`. The purpose of the tool is to take assets created +in digital content creation tools like `Maya`_, `3ds Max`_ or `Blender`_ and +converts them into an efficient runtime format for use with Qt Quick 3D. + +For more information on the further capabilities of the tool, read Qt's +documentation here: `Balsam Asset Import Tool`_. + +.. image:: pyside6-balsamui_screenshot.webp + :width: 500 + :alt: pyside6-balsamui screenshot + +.. _`Balsam Asset Import Tool`: https://doc.qt.io/qt-6/qtquick3d-tool-balsam.html +.. _Maya: https://www.autodesk.com/products/maya/overview +.. _3ds Max: https://www.autodesk.com/products/3ds-max/overview +.. _Blender: https://www.blender.org/ + diff --git a/sources/pyside6/doc/tools/pyside6-balsamui_screenshot.webp b/sources/pyside6/doc/tools/pyside6-balsamui_screenshot.webp new file mode 100644 index 00000000..5c194fdb Binary files /dev/null and b/sources/pyside6/doc/tools/pyside6-balsamui_screenshot.webp differ diff --git a/sources/pyside6/doc/tools/pyside6-designer_base_screenshot.webp b/sources/pyside6/doc/tools/pyside6-designer_base_screenshot.webp new file mode 100644 index 00000000..88ff7778 Binary files /dev/null and b/sources/pyside6/doc/tools/pyside6-designer_base_screenshot.webp differ diff --git a/sources/pyside6/doc/tools/pyside6-designer_customwidgets_screenshot.webp b/sources/pyside6/doc/tools/pyside6-designer_customwidgets_screenshot.webp new file mode 100644 index 00000000..c84436c1 Binary files /dev/null and b/sources/pyside6/doc/tools/pyside6-designer_customwidgets_screenshot.webp differ diff --git a/sources/pyside6/doc/tools/pyside6-designer_screenshot.webp b/sources/pyside6/doc/tools/pyside6-designer_screenshot.webp new file mode 100644 index 00000000..27f03652 Binary files /dev/null and b/sources/pyside6/doc/tools/pyside6-designer_screenshot.webp differ diff --git a/sources/pyside6/doc/tools/pyside6-designer_sections_screenshot.webp b/sources/pyside6/doc/tools/pyside6-designer_sections_screenshot.webp new file mode 100644 index 00000000..6ff02550 Binary files /dev/null and b/sources/pyside6/doc/tools/pyside6-designer_sections_screenshot.webp differ diff --git a/sources/pyside6/doc/tools/pyside6-linguist_screenshot.webp b/sources/pyside6/doc/tools/pyside6-linguist_screenshot.webp new file mode 100644 index 00000000..cd91007b Binary files /dev/null and b/sources/pyside6/doc/tools/pyside6-linguist_screenshot.webp differ diff --git a/sources/pyside6/doc/tools/pyside6-qmlimportscanner.rst b/sources/pyside6/doc/tools/pyside6-qmlimportscanner.rst new file mode 100644 index 00000000..ba27414d --- /dev/null +++ b/sources/pyside6/doc/tools/pyside6-qmlimportscanner.rst @@ -0,0 +1,53 @@ +.. _pyside6-qmlimportscanner: + +pyside6-qmlimportscanner +======================== + +``pyside6-qmlimportscanner`` is a command line tool that wraps the +``qmlimportscanner`` tool of Qt. + + +The tool is automatically run by the :ref:`pyside6-project` tool +when passing the ``qmllint`` argument instructing it to check +the QML source files. + +Usage +----- + +Invoking the tool in the directory of the :ref:`filesystemexplorer_example` +example using: + +.. code-block:: bash + + pyside6-qmlimportscanner -rootPath . + +produces: + +.. code-block:: json + + [ + { + "name": "QtQuick", + "type": "module" + }, + { + "name": "QtQuick.Controls.Basic", + "type": "module" + }, + { + "name": "QtQuick.Layouts", + "type": "module" + }, + { + "name": "FileSystemModule", + "type": "module" + }, + { + "name": "QtQuick.Controls", + "type": "module" + }, + { + "name": "QtQuick.Effects", + "type": "module" + } + ] diff --git a/sources/pyside6/doc/tools/pyside6-qsb.rst b/sources/pyside6/doc/tools/pyside6-qsb.rst new file mode 100644 index 00000000..f6f1847d --- /dev/null +++ b/sources/pyside6/doc/tools/pyside6-qsb.rst @@ -0,0 +1,39 @@ +.. _pyside6-qsb: + +pyside6-qsb +=========== + +``pyside6-qsb`` is a tool that wraps the `qsb `_ tool. qsb is a +command line tool provided by the `Qt Shader Tools`_ module. It integrates +third-party libraries such as `glslang`_ and `SPIRV-Cross`_, optionally invokes +external tools, such as ``fxc`` or ``spirv-opt``, and generates .qsb files. +Additionally, it can be used to inspect the contents of a .qsb package. + +For more information on how to use this tool, read Qt's documentation +here: `QSB Manual`_. + +Usage +----- + +To create a qsb file from a shader file, e.g., ``shader.frag``, use the +following command: + +.. code-block:: bash + + pyside6-qsb -o shader.frag.qsb shader.frag + +To inspect the file produced, i.e., ``shader.frag.qsb``, use the following +command: + +.. code-block:: bash + + pyside6-qsb -d shader.frag.qsb + +This will print the reflection metadata (in JSON form) and the included shaders. + +For other modes of operation, refer to the `QSB Manual`_. + +.. _`glslang`: https://github.com/KhronosGroup/glslang +.. _`spirv-cross`: https://github.com/KhronosGroup/SPIRV-Cross +.. _`QSB Manual`: https://doc.qt.io/qt-6/qtshadertools-qsb.html +.. _`Qt Shader Tools`: https://doc.qt.io/qt-6/qtshadertools-index.html diff --git a/sources/pyside6/doc/tutorials/basictutorial/qrcfiles.rst b/sources/pyside6/doc/tutorials/basictutorial/qrcfiles.rst index 21f4e1e2..858293be 100644 --- a/sources/pyside6/doc/tutorials/basictutorial/qrcfiles.rst +++ b/sources/pyside6/doc/tutorials/basictutorial/qrcfiles.rst @@ -46,7 +46,6 @@ In the following example, notice how the resources are listed in ``icons.qrc`` :: - icons/play.png diff --git a/sources/pyside6/doc/tutorials/basictutorial/signals_and_slots.rst b/sources/pyside6/doc/tutorials/basictutorial/signals_and_slots.rst index 9c216da6..0bfd9e27 100644 --- a/sources/pyside6/doc/tutorials/basictutorial/signals_and_slots.rst +++ b/sources/pyside6/doc/tutorials/basictutorial/signals_and_slots.rst @@ -79,7 +79,7 @@ Signals can also be connected to free functions: Connections can be spelled out in code or, for widget forms, designed in the `Signal-Slot Editor `_ -of Qt Designer. +of *Qt Widgets Designer*. The Signal Class ---------------- @@ -139,6 +139,9 @@ useful for QML applications to refer to the emitted values by name: // do something with 'sum' } + +.. _slot-decorator: + The Slot Class -------------- @@ -223,6 +226,8 @@ the different functionality. someone.speak[str].emit("Hello everybody!") +.. _signals-and-slots-strings: + Specifying Signals and Slots by Method Signature Strings -------------------------------------------------------- @@ -235,12 +240,24 @@ strings passed through the ``SIGNAL()`` and/or ``SLOT()`` functions: from PySide6.QtCore import SIGNAL, SLOT button.connect(SIGNAL("clicked(Qt::MouseButton)"), - action_handler, SLOT("action1(Qt::MouseButton)")) + action_handler, SLOT("action1(Qt::MouseButton)")) -This is not recommended for connecting signals, it is mostly -used to specify signals for methods like ``QWizardPage::registerField()``: +This is not normally recommended; it is only needed +for a few cases where signals are only accessible via ``QMetaObject`` +(``QAxObject``, ``QAxWidget``, ``QDBusInterface`` or ``QWizardPage::registerField()``): .. code-block:: python wizard.registerField("text", line_edit, "text", SIGNAL("textChanged(QString)")) + +The signature strings can be found by querying ``QMetaMethod.methodSignature()`` +when introspecting ``QMetaObject``: + +.. code-block:: python + + mo = widget.metaObject() + for m in range(mo.methodOffset(), mo.methodCount()): + print(mo.method(m).methodSignature()) + +Slots should be decorated using :ref:`@Slot `. diff --git a/sources/pyside6/doc/tutorials/basictutorial/translations.rst b/sources/pyside6/doc/tutorials/basictutorial/translations.rst index 6ca403ba..21c16cdc 100644 --- a/sources/pyside6/doc/tutorials/basictutorial/translations.rst +++ b/sources/pyside6/doc/tutorials/basictutorial/translations.rst @@ -54,13 +54,27 @@ they should be passed to the ``pyside6-lupdate`` tool as well: The source files generated by ``pyside6-uic`` from the form files should **not** be passed. +The ``lupdate`` mode of ``pyside6-project`` can also be used for this. It +collects all source files and runs ``pyside6-lupdate`` when ``.ts`` file(s) +are given in the ``.pyproject`` file: + +.. code-block:: bash + + pyside6-project lupdate . + ``.ts`` files are translated using *Qt Linguist*. Once this is complete, the files are converted to a binary form (``.qm`` files): .. code-block:: bash - mkdir translations - pyside6-lrelease example_de.ts -qm translations/example_de.qm + pyside6-lrelease example_de.ts -qm example_de.qm + +``pyside6-project`` will build the ``.qm`` file automatically when +``.ts`` file(s) are given in the ``.pyproject`` file: + +.. code-block:: bash + + pyside6-project build . To avoid having to ship the ``.qm`` files, it is recommend to put them into a Qt resource file along with icons and other @@ -71,8 +85,8 @@ under ``:/translations``: .. code-block:: xml - - translations/example_de.qm + + example_de.qm diff --git a/sources/pyside6/doc/tutorials/basictutorial/uifiles.rst b/sources/pyside6/doc/tutorials/basictutorial/uifiles.rst index c56593ee..cb945908 100644 --- a/sources/pyside6/doc/tutorials/basictutorial/uifiles.rst +++ b/sources/pyside6/doc/tutorials/basictutorial/uifiles.rst @@ -4,13 +4,13 @@ Using ``.ui`` files from Designer or QtCreator with ``QUiLoader`` and ``pyside6- ************************************************************************************* This page describes the use of -`Qt Designer `_ to create +`Qt Widgets Designer `_ to create graphical interfaces based on Qt Widgets for your Qt for Python project. -**Qt Designer** is a graphical UI design tool which is available as a +*Qt Widgets Designer* is a graphical UI design tool which is available as a standalone binary (``pyside6-designer``) or embedded into the -`Qt Creator IDE `_. Its use within **Qt Creator** +`Qt Creator IDE `_. Its use within *Qt Creator* is described at -`Using Qt Designer `_. +`Using Qt Widgets Designer `_. .. image:: uifiles.png :alt: Designer and the equivalent code @@ -19,7 +19,7 @@ The designs are stored in ``.ui`` files, which is an XML-based format. It will be converted to Python or C++ code populating a widget instance at project build time by the `pyside6-uic `_ tool. -To create a new Qt Design Form in **Qt Creator**, choose +To create a new Qt Design Form in *Qt Creator*, choose ``File/New File Or Project`` and "Main Window" for template. Save it as ``mainwindow.ui``. Add a ``QPushButton`` to the center of the centralwidget. @@ -206,24 +206,25 @@ command prompt: .. _designer_custom_widgets: -Custom Widgets in Qt Designer -============================= +Custom Widgets in Qt Widgets Designer +===================================== -**Qt Designer** is able to use user-provided (custom) widgets. They are shown -in the widget box and can be dragged onto the form just like Qt's widgets (see -`Using Custom Widgets with Qt Designer `_ -). Normally, this requires implementing the widget as a plugin to Qt Designer -written in C++ implementing its +*Qt Widgets Designer* is able to use user-provided (custom) widgets. +They are shown in the widget box and can be dragged onto the form just like +Qt's widgets (see +`Using Custom Widgets with Qt Widgets Designer `_ +). Normally, this requires implementing the widget as a plugin to +*Qt Widgets Designer* written in C++ implementing its `QDesignerCustomWidgetInterface`_ . Qt for Python provides a simple interface for this which is similar to :meth:`registerCustomWidget()`. The widget needs to be provided as a Python module, as shown by -the widgetbinding example (file ``wigglywidget.py``) or -the taskmenuextension example (file ``tictactoe.py``). +the :ref:`widgetbinding-example` (file ``wigglywidget.py``) or +the :ref:`task-menu-extension-example` (file ``tictactoe.py``). -Registering this with Qt Designer is done by providing +Registering this with *Qt Widgets Designer* is done by providing a registration script named ``register*.py`` and pointing the path-type environment variable ``PYSIDE_DESIGNER_PLUGINS`` to the directory. @@ -263,19 +264,19 @@ The code of the registration script looks as follows: QPyDesignerCustomWidgetCollection provides an implementation of `QDesignerCustomWidgetCollectionInterface`_ -exposing custom widgets to **Qt Designer** with static convenience functions -for registering types or adding instances of +exposing custom widgets to *Qt Widgets Designer* with static convenience +functions for registering types or adding instances of `QDesignerCustomWidgetInterface`_ . The function :meth:`registerCustomWidget()` -is used to register a widget type with **Qt Designer**. In the simple case, it +is used to register a widget type with *Qt Widgets Designer*. In the simple case, it can be used like ``QUiLoader.registerCustomWidget()``. It takes the custom widget type and some optional keyword arguments passing values that correspond to the getters of `QDesignerCustomWidgetInterface`_ : -When launching **Qt Designer** via its launcher ``pyside6-designer``, +When launching *Qt Widgets Designer* via its launcher ``pyside6-designer``, the custom widget should be visible in the widget box. For advanced usage, it is also possible to pass the function an implementation @@ -289,15 +290,15 @@ corresponding C++ .. _QDesignerCustomWidgetCollectionInterface: https://doc.qt.io/qt-6/qdesignercustomwidgetcollectioninterface.html .. _QDesignerCustomWidgetInterface: https://doc.qt.io/qt-6/qdesignercustomwidgetinterface.html -Troubleshooting the Qt Designer Plugin -++++++++++++++++++++++++++++++++++++++ +Troubleshooting the Qt Widgets Designer Plugin +++++++++++++++++++++++++++++++++++++++++++++++ - The launcher ``pyside6-designer`` must be used. The standalone - **Qt Designer** will not load the plugin. + *Qt Widgets Designer* will not load the plugin. - The menu item **Help/About Plugin** brings up a dialog showing the plugins found and potential load error messages. - Check the console or Windows Debug view for further error messages. - Due to the buffering of output by Python, error messages may appear - only after **Qt Designer** has terminated. + only after *Qt Widgets Designer* has terminated. - When building Qt for Python, be sure to set the ``--standalone`` option for the plugin to be properly installed. diff --git a/sources/pyside6/doc/tutorials/debugging/mixed_debugging.rst b/sources/pyside6/doc/tutorials/debugging/mixed_debugging.rst index d425b701..85c32634 100644 --- a/sources/pyside6/doc/tutorials/debugging/mixed_debugging.rst +++ b/sources/pyside6/doc/tutorials/debugging/mixed_debugging.rst @@ -5,10 +5,10 @@ When debugging PySide code, very often you would also like to debug the corresponding C++ extension of the PySide module. This is done by attaching your debugger to the Python interpreter. In this tutorial, we are going to take you through a comprehensive guide in building Qt 6, using the built Qt 6 to build -PySide6, and then starting a debugging process in either Qt Creator or VSCode. +PySide6, and then starting a debugging process in either *Qt Creator* or VSCode. With VSCode, you should be able to see the combined call stacks for both C++ and -Python together. With Qt Creator, unfortunately you would only be able to +Python together. With *Qt Creator*, unfortunately you would only be able to debug the native C++ code of the PySide module; that is you won't be able to set breakpoints inside the Python code. @@ -116,7 +116,7 @@ It is recommended to use a Python virtual environment rather than installing in Debugging the process using your preferred IDE ---------------------------------------------- -The following sections guide you through the setup for Qt Creator or VSCode. +The following sections guide you through the setup for *Qt Creator* or VSCode. .. toctree:: :glob: diff --git a/sources/pyside6/doc/tutorials/debugging/qml_debugging.rst b/sources/pyside6/doc/tutorials/debugging/qml_debugging.rst index 474abe50..223e608f 100644 --- a/sources/pyside6/doc/tutorials/debugging/qml_debugging.rst +++ b/sources/pyside6/doc/tutorials/debugging/qml_debugging.rst @@ -1,7 +1,7 @@ Using Qt Creator's QML Debugger for a PySide6 QML Application ************************************************************* -Besides the C++ debugger, Qt Creator provides a `QML debugger`_ which lets you +Besides the C++ debugger, *Qt Creator* provides a `QML debugger`_ which lets you inspect JavaScript code. It works by connecting to a socket server run by the ``QmlEngine`` instance. The port is passed on the command line. To enable it, add the below code to your QML application: diff --git a/sources/pyside6/doc/tutorials/debugging/qtcreator/qtcreator.rst b/sources/pyside6/doc/tutorials/debugging/qtcreator/qtcreator.rst index a35020fd..a78a67fa 100644 --- a/sources/pyside6/doc/tutorials/debugging/qtcreator/qtcreator.rst +++ b/sources/pyside6/doc/tutorials/debugging/qtcreator/qtcreator.rst @@ -1,9 +1,9 @@ Debugging PySide with Qt Creator (Linux) **************************************** -As opposed to VSCode, presently Qt Creator does not support mixed mode debugging. +As opposed to VSCode, presently *Qt Creator* does not support mixed mode debugging. However, we can debug the C++ implementation of the corresponding Python PySide -code. Unlike VSCode, Qt Creator provides a very easy interface to attach GDB to +code. Unlike VSCode, *Qt Creator* provides a very easy interface to attach GDB to the Python interpreter. It saves you from doing all the extra configuration steps, that have to be done with VSCode. diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/Main.qml b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/Main.qml index b699720d..faa9175d 100644 --- a/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/Main.qml +++ b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/Main.qml @@ -6,46 +6,68 @@ import QtQuick.Controls.Basic import QtQuick.Layouts import FileSystemModule +pragma ComponentBehavior: Bound + ApplicationWindow { id: root + + property bool expandPath: false + property bool showLineNumbers: true + property string currentFilePath: "" + width: 1100 height: 600 + minimumWidth: 200 + minimumHeight: 100 visible: true + color: Colors.background flags: Qt.Window | Qt.FramelessWindowHint - title: qsTr("Qt Quick Controls - File System Explorer") + title: qsTr("File System Explorer Example") - property string currentFilePath: "" - property bool expandPath: false + function getInfoText() : string { + let out = root.currentFilePath + if (!out) + return qsTr("File System Explorer") + return root.expandPath ? out : out.substring(out.lastIndexOf("/") + 1, out.length) + } menuBar: MyMenuBar { - rootWindow: root - - infoText: currentFilePath - ? (expandPath ? currentFilePath - : currentFilePath.substring(currentFilePath.lastIndexOf("/") + 1, currentFilePath.length)) - : "File System Explorer" - + dragWindow: root + infoText: root.getInfoText() MyMenu { title: qsTr("File") Action { text: qsTr("Increase Font") - shortcut: "Ctrl++" - onTriggered: textArea.font.pixelSize += 1 + shortcut: StandardKey.ZoomIn + onTriggered: editor.text.font.pixelSize += 1 } Action { text: qsTr("Decrease Font") - shortcut: "Ctrl+-" - onTriggered: textArea.font.pixelSize -= 1 + shortcut: StandardKey.ZoomOut + onTriggered: editor.text.font.pixelSize -= 1 } Action { - text: expandPath ? qsTr("Toggle Short Path") : qsTr("Toggle Expand Path") - enabled: currentFilePath - onTriggered: expandPath = !expandPath + text: root.showLineNumbers ? qsTr("Toggle Line Numbers OFF") + : qsTr("Toggle Line Numbers ON") + shortcut: "Ctrl+L" + onTriggered: root.showLineNumbers = !root.showLineNumbers + } + Action { + text: root.expandPath ? qsTr("Toggle Short Path") + : qsTr("Toggle Expand Path") + enabled: root.currentFilePath + onTriggered: root.expandPath = !root.expandPath + } + Action { + text: qsTr("Reset Filesystem") + enabled: sidebar.currentTabIndex === 1 + onTriggered: fileSystemView.rootIndex = undefined } Action { text: qsTr("Exit") onTriggered: Qt.exit(0) + shortcut: StandardKey.Quit } } @@ -55,149 +77,120 @@ ApplicationWindow { Action { text: qsTr("Cut") shortcut: StandardKey.Cut - enabled: textArea.selectedText.length > 0 - onTriggered: textArea.cut() + enabled: editor.text.selectedText.length > 0 + onTriggered: editor.text.cut() } Action { text: qsTr("Copy") shortcut: StandardKey.Copy - enabled: textArea.selectedText.length > 0 - onTriggered: textArea.copy() + enabled: editor.text.selectedText.length > 0 + onTriggered: editor.text.copy() } Action { text: qsTr("Paste") shortcut: StandardKey.Paste - enabled: textArea.canPaste - onTriggered: textArea.paste() + enabled: editor.text.canPaste + onTriggered: editor.text.paste() } Action { text: qsTr("Select All") shortcut: StandardKey.SelectAll - enabled: textArea.length > 0 - onTriggered: textArea.selectAll() + enabled: editor.text.length > 0 + onTriggered: editor.text.selectAll() } Action { text: qsTr("Undo") shortcut: StandardKey.Undo - enabled: textArea.canUndo - onTriggered: textArea.undo() + enabled: editor.text.canUndo + onTriggered: editor.text.undo() } } } - - Rectangle { + // Set up the layout of the main components in a row: + // [ Sidebar, Navigation, Editor ] + RowLayout { anchors.fill: parent - color: Colors.background - - RowLayout { - anchors.fill: parent - spacing: 0 - - // Stores the buttons that navigate the application. - Sidebar { - id: sidebar - z: 2 - rootWindow: root - - Layout.preferredWidth: 60 - Layout.fillHeight: true - } + spacing: 0 + + // Stores the buttons that navigate the application. + Sidebar { + id: sidebar + dragWindow: root + Layout.preferredWidth: 50 + Layout.fillHeight: true + } - // Allows resizing parts of the UI. - SplitView { - Layout.fillWidth: true - Layout.fillHeight: true - - handle: Rectangle { - implicitWidth: 10 - color: SplitHandle.pressed ? Colors.color2 : Colors.background - border.color: Colors.color2 - opacity: SplitHandle.hovered || SplitHandle.pressed ? 1.0 : 0.0 - - Behavior on opacity { - OpacityAnimator { - duration: 900 - } + // Allows resizing parts of the UI. + SplitView { + Layout.fillWidth: true + Layout.fillHeight: true + // Customized handle to drag between the Navigation and the Editor. + handle: Rectangle { + implicitWidth: 10 + color: SplitHandle.pressed ? Colors.color2 : Colors.background + border.color: SplitHandle.hovered ? Colors.color2 : Colors.background + opacity: SplitHandle.hovered || navigationView.width < 15 ? 1.0 : 0.0 + + Behavior on opacity { + OpacityAnimator { + duration: 1400 } } + } - // We use an inline component to make a reusable TextArea component. - // This is convenient when the component is only used in one file. - component MyTextArea: TextArea { - antialiasing: true - color: Colors.textFile - selectedTextColor: Colors.textFile - selectionColor: Colors.selection - renderType: Text.QtRendering - textFormat: TextEdit.PlainText - - background: null - } - - Rectangle { - z: 1 - color: Colors.surface1 - - SplitView.preferredWidth: 250 - SplitView.fillHeight: true - - StackLayout { - currentIndex: sidebar.currentTabIndex > 1 ? 1 : sidebar.currentTabIndex - - anchors.fill: parent - - // Shows the help text. - MyTextArea { - readOnly: true - text: qsTr("This example shows how to use and visualize the file system.\n\n" - + "Customized Qt Quick Components have been used to achieve this look.\n\n" - + "You can edit the files but they won't be changed on the file system.\n\n" - + "Click on the folder icon to the left to get started.") - wrapMode: TextArea.Wrap - } - - // Shows the files on the file system. - FileSystemView { - id: fileSystemView - color: Colors.surface1 + Rectangle { + id: navigationView + color: Colors.surface1 + SplitView.preferredWidth: 250 + SplitView.fillHeight: true + // The stack-layout provides different views, based on the + // selected buttons inside the sidebar. + StackLayout { + anchors.fill: parent + currentIndex: sidebar.currentTabIndex > 1 ? 1 : sidebar.currentTabIndex + + // Shows the help text. + Text { + text: qsTr("This example shows how to use and visualize the file system.\n\n" + + "Customized Qt Quick Components have been used to achieve this look.\n\n" + + "You can edit the files but they won't be changed on the file system.\n\n" + + "Click on the folder icon to the left to get started.") + wrapMode: TextArea.Wrap + color: Colors.text + } - onFileClicked: (path) => root.currentFilePath = path - } + // Shows the files on the file system. + FileSystemView { + id: fileSystemView + color: Colors.surface1 + onFileClicked: path => root.currentFilePath = path } } + } - // The ScrollView that contains the TextArea which shows the file's content. - StackLayout { - currentIndex: sidebar.currentTabIndex > 1 ? 1 : 0 - - SplitView.fillWidth: true - SplitView.fillHeight: true - // TextArea is the first element inside the stack - ScrollView { - Layout.fillWidth: true - Layout.fillHeight: true - - leftPadding: 20 - topPadding: 20 - bottomPadding: 20 + // The main view that contains the editor or the scheme-manager. + StackLayout { + currentIndex: sidebar.currentTabIndex > 1 ? 1 : 0 - clip: true + SplitView.fillWidth: true + SplitView.fillHeight: true - property alias textArea: textArea + Editor { + id: editor + showLineNumbers: root.showLineNumbers + currentFilePath: root.currentFilePath + } - MyTextArea { - id: textArea - text: FileSystemModel.readFile(root.currentFilePath) - } - } - // The ColorScheme is the second element in the stack - ColorScheme { - Layout.fillWidth: true - Layout.fillHeight: true - } + ColorScheme { + Layout.fillWidth: true + Layout.fillHeight: true } + } } - ResizeButton {} + } + + ResizeButton { + resizeWindow: root } } diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/app.qrc b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/app.qrc index 7303ced8..ccd9eefe 100644 --- a/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/app.qrc +++ b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/app.qrc @@ -3,7 +3,8 @@ qmldir Main.qml qml/About.qml - qml/ColorScheme.qml + qml/Editor.qml + qml/Colors.qml qml/FileSystemView.qml qml/Icon.qml qml/MyMenu.qml diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons.qrc b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons.qrc index 61323cef..69bddc01 100644 --- a/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons.qrc +++ b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons.qrc @@ -1,14 +1,15 @@ + icons/app_icon.svg icons/folder_closed.svg icons/folder_open.svg icons/generic_file.svg icons/globe.svg icons/info_sign.svg + icons/leaf.svg icons/light_bulb.svg icons/qt_logo.svg icons/read.svg icons/resize.svg - icons/leaf.svg diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons/app_icon.svg b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons/app_icon.svg new file mode 100644 index 00000000..5aae4221 --- /dev/null +++ b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons/app_icon.svg @@ -0,0 +1,2 @@ + + diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/About.qml b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/About.qml index b7bc0ac6..178bf03e 100644 --- a/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/About.qml +++ b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/About.qml @@ -7,53 +7,87 @@ import FileSystemModule ApplicationWindow { id: root - width: 500 - height: 360 + width: 650 + height: 550 flags: Qt.Window | Qt.FramelessWindowHint color: Colors.surface1 menuBar: MyMenuBar { id: menuBar - implicitHeight: 20 - rootWindow: root + + dragWindow: root + implicitHeight: 27 infoText: "About Qt" } Image { id: logo + anchors.left: parent.left anchors.right: parent.right anchors.top: parent.top anchors.margins: 20 + source: "../icons/qt_logo.svg" - sourceSize: Qt.size(80, 80) + sourceSize.width: 80 + sourceSize.height: 80 fillMode: Image.PreserveAspectFit + smooth: true antialiasing: true asynchronous: true } - TextArea { - anchors.top: logo.bottom - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: parent.bottom - anchors.margins: 20 - antialiasing: true - wrapMode: Text.WrapAnywhere - color: Colors.textFile - horizontalAlignment: Text.AlignHCenter - readOnly: true - selectionColor: Colors.selection - text: qsTr("Qt Group (Nasdaq Helsinki: QTCOM) is a global software company with a strong \ -presence in more than 70 industries and is the leading independent technology behind 1+ billion \ -devices and applications. Qt is used by major global companies and developers worldwide, and the \ -technology enables its customers to deliver exceptional user experiences and advance their digital \ -transformation initiatives. Qt achieves this through its cross-platform software framework for the \ -development of apps and devices, under both commercial and open-source licenses.") - background: Rectangle { - color: "transparent" - } + ScrollView { + anchors.top: logo.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.margins: 20 + + TextArea { + selectedTextColor: Colors.textFile + selectionColor: Colors.selection + horizontalAlignment: Text.AlignHCenter + textFormat: Text.RichText + + text: qsTr("

About Qt

" + + "

This program uses Qt version %1.

" + + "

Qt is a C++ toolkit for cross-platform application " + + "development.

" + + "

Qt provides single-source portability across all major desktop " + + "operating systems. It is also available for embedded Linux and other " + + "embedded and mobile operating systems.

" + + "

Qt is available under multiple licensing options designed " + + "to accommodate the needs of our various users.

" + + "

Qt licensed under our commercial license agreement is appropriate " + + "for development of proprietary/commercial software where you do not " + + "want to share any source code with third parties or otherwise cannot " + + "comply with the terms of GNU (L)GPL.

" + + "

Qt licensed under GNU (L)GPL is appropriate for the " + + "development of Qt applications provided you can comply with the terms " + + "and conditions of the respective licenses.

" + + "

Please see %2 " + + "for an overview of Qt licensing.

" + + "

Copyright (C) %3 The Qt Company Ltd and other " + + "contributors.

" + + "

Qt and the Qt logo are trademarks of The Qt Company Ltd.

" + + "

Qt is The Qt Company Ltd product developed as an open source " + + "project. See %4 for more information.

") + .arg(Application.version).arg("qt.io/licensing").arg("2023").arg("qt.io") + color: Colors.textFile + wrapMode: Text.WordWrap + readOnly: true + antialiasing: true + background: null + + onLinkActivated: function(link) { + Qt.openUrlExternally(link) + } + } + } + + ResizeButton { + resizeWindow: root } - ResizeButton {} } diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/Editor.qml b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/Editor.qml new file mode 100644 index 00000000..80f7c04c --- /dev/null +++ b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/Editor.qml @@ -0,0 +1,160 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import FileSystemModule + +pragma ComponentBehavior: Bound + +// This is the text editor that displays the currently open file, including +// their corresponding line numbers. +Rectangle { + id: root + + required property string currentFilePath + required property bool showLineNumbers + property alias text: textArea + property int currentLineNumber: -1 + property int rowHeight: Math.ceil(fontMetrics.lineSpacing) + + color: Colors.background + + onWidthChanged: textArea.update() + onHeightChanged: textArea.update() + + RowLayout { + anchors.fill: parent + // We use a flickable to synchronize the position of the editor and + // the line numbers. This is necessary because the line numbers can + // extend the available height. + Flickable { + id: lineNumbers + + // Calculate the width based on the logarithmic scale. + Layout.preferredWidth: fontMetrics.averageCharacterWidth + * (Math.floor(Math.log10(textArea.lineCount)) + 1) + 10 + Layout.fillHeight: true + + interactive: false + contentY: editorFlickable.contentY + visible: textArea.text !== "" && root.showLineNumbers + + Column { + anchors.fill: parent + Repeater { + id: repeatedLineNumbers + + model: LineNumberModel { + lineCount: textArea.text !== "" ? textArea.lineCount : 0 + } + + delegate: Item { + required property int index + + width: parent.width + height: root.rowHeight + Label { + id: numbers + + text: parent.index + 1 + + width: parent.width + height: parent.height + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + + color: (root.currentLineNumber === parent.index) + ? Colors.iconIndicator : Qt.darker(Colors.text, 2) + font: textArea.font + } + Rectangle { + id: indicator + + anchors.left: numbers.right + width: 1 + height: parent.height + color: Qt.darker(Colors.text, 3) + } + } + } + } + } + + Flickable { + id: editorFlickable + + property alias textArea: textArea + + // We use an inline component to customize the horizontal and vertical + // scroll-bars. This is convenient when the component is only used in one file. + component MyScrollBar: ScrollBar { + id: scrollBar + background: Rectangle { + implicitWidth: scrollBar.interactive ? 8 : 4 + implicitHeight: scrollBar.interactive ? 8 : 4 + + opacity: scrollBar.active && scrollBar.size < 1.0 ? 1.0 : 0.0 + color: Colors.background + Behavior on opacity { + OpacityAnimator { + duration: 500 + } + } + } + contentItem: Rectangle { + implicitWidth: scrollBar.interactive ? 8 : 4 + implicitHeight: scrollBar.interactive ? 8 : 4 + opacity: scrollBar.active && scrollBar.size < 1.0 ? 1.0 : 0.0 + color: Colors.color1 + Behavior on opacity { + OpacityAnimator { + duration: 1000 + } + } + } + } + + Layout.fillHeight: true + Layout.fillWidth: true + ScrollBar.horizontal: MyScrollBar {} + ScrollBar.vertical: MyScrollBar {} + + boundsBehavior: Flickable.StopAtBounds + + TextArea.flickable: TextArea { + id: textArea + anchors.fill: parent + + focus: false + topPadding: 0 + leftPadding: 10 + + text: FileSystemModel.readFile(root.currentFilePath) + tabStopDistance: fontMetrics.averageCharacterWidth * 4 + + // Grab the current line number from the C++ interface. + onCursorPositionChanged: { + root.currentLineNumber = FileSystemModel.currentLineNumber( + textArea.textDocument, textArea.cursorPosition) + } + + color: Colors.textFile + selectedTextColor: Colors.textFile + selectionColor: Colors.selection + + textFormat: TextEdit.PlainText + renderType: Text.QtRendering + selectByMouse: true + antialiasing: true + background: null + } + + FontMetrics { + id: fontMetrics + font: textArea.font + } + } + } +} diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/FileSystemView.qml b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/FileSystemView.qml index ade2e48c..db955168 100644 --- a/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/FileSystemView.qml +++ b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/FileSystemView.qml @@ -2,26 +2,31 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause import QtQuick -import QtQuick.Layouts +import QtQuick.Effects import QtQuick.Controls.Basic import FileSystemModule +pragma ComponentBehavior: Bound + // This is the file system view which gets populated by the C++ model. Rectangle { id: root signal fileClicked(string filePath) + property alias rootIndex: fileSystemTreeView.rootIndex TreeView { id: fileSystemTreeView + + property int lastIndex: -1 + anchors.fill: parent model: FileSystemModel + rootIndex: FileSystemModel.rootIndex boundsBehavior: Flickable.StopAtBounds boundsMovement: Flickable.StopAtBounds clip: true - property int lastIndex: -1 - Component.onCompleted: fileSystemTreeView.toggleExpanded(0) // The delegate represents a single entry in the filesystem. @@ -31,50 +36,101 @@ Rectangle { implicitWidth: fileSystemTreeView.width > 0 ? fileSystemTreeView.width : 250 implicitHeight: 25 + // Since we have the 'ComponentBehavior Bound' pragma, we need to + // require these properties from our model. This is a convenient way + // to bind the properties provided by the model's role names. required property int index required property url filePath + required property string fileName - indicator: null - - contentItem: Item { - anchors.fill: parent + indicator: Image { + id: directoryIcon - Icon { - id: directoryIcon - x: leftMargin + (depth * indentation) - anchors.verticalCenter: parent.verticalCenter - path: treeDelegate.hasChildren - ? (treeDelegate.expanded ? "../icons/folder_open.svg" : "../icons/folder_closed.svg") + x: treeDelegate.leftMargin + (treeDelegate.depth * treeDelegate.indentation) + anchors.verticalCenter: parent.verticalCenter + source: treeDelegate.hasChildren ? (treeDelegate.expanded + ? "../icons/folder_open.svg" : "../icons/folder_closed.svg") : "../icons/generic_file.svg" - iconColor: (treeDelegate.expanded && treeDelegate.hasChildren) ? Colors.color2 : Colors.folder - } - Text { - anchors.left: directoryIcon.right - anchors.verticalCenter: parent.verticalCenter - width: parent.width - text: model.fileName - color: Colors.text - } + sourceSize.width: 20 + sourceSize.height: 20 + fillMode: Image.PreserveAspectFit + + smooth: true + antialiasing: true + asynchronous: true + } + + contentItem: Text { + text: treeDelegate.fileName + color: Colors.text } background: Rectangle { - color: treeDelegate.index === fileSystemTreeView.lastIndex + color: (treeDelegate.index === fileSystemTreeView.lastIndex) ? Colors.selection : (hoverHandler.hovered ? Colors.active : "transparent") } - TapHandler { - onSingleTapped: { - fileSystemTreeView.toggleExpanded(row) - fileSystemTreeView.lastIndex = index - // If this model item doesn't have children, it means it's representing a file. - if (!treeDelegate.hasChildren) - root.fileClicked(filePath) + // We color the directory icons with this MultiEffect, where we overlay + // the colorization color ontop of the SVG icons. + MultiEffect { + id: iconOverlay + + anchors.fill: directoryIcon + source: directoryIcon + colorization: 1.0 + brightness: 1.0 + colorizationColor: { + const isFile = treeDelegate.index === fileSystemTreeView.lastIndex + && !treeDelegate.hasChildren; + if (isFile) + return Qt.lighter(Colors.folder, 3) + + const isExpandedFolder = treeDelegate.expanded && treeDelegate.hasChildren; + if (isExpandedFolder) + return Colors.color2 + else + return Colors.folder } } + HoverHandler { id: hoverHandler } + + TapHandler { + acceptedButtons: Qt.LeftButton | Qt.RightButton + onSingleTapped: (eventPoint, button) => { + switch (button) { + case Qt.LeftButton: + fileSystemTreeView.toggleExpanded(treeDelegate.row) + fileSystemTreeView.lastIndex = treeDelegate.index + // If this model item doesn't have children, it means it's + // representing a file. + if (!treeDelegate.hasChildren) + root.fileClicked(treeDelegate.filePath) + break; + case Qt.RightButton: + if (treeDelegate.hasChildren) + contextMenu.popup(); + break; + } + } + } + + MyMenu { + id: contextMenu + Action { + text: qsTr("Set as root index") + onTriggered: { + fileSystemTreeView.rootIndex = fileSystemTreeView.index(treeDelegate.row, 0) + } + } + Action { + text: qsTr("Reset root index") + onTriggered: fileSystemTreeView.rootIndex = undefined + } + } } // Provide our own custom ScrollIndicator for the TreeView. @@ -85,6 +141,7 @@ Rectangle { contentItem: Rectangle { implicitWidth: 6 implicitHeight: 6 + color: Colors.color1 opacity: fileSystemTreeView.movingVertically ? 0.5 : 0.0 diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/Icon.qml b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/Icon.qml deleted file mode 100644 index 25162d9d..00000000 --- a/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/Icon.qml +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (C) 2023 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -import QtQuick -import QtQuick.Effects - -// Custom Component for displaying Icons -Item { - id: root - - required property url path - property real padding: 5 - property real size: 30 - property alias iconColor: overlay.colorizationColor - property alias hovered: mouse.hovered - - width: size - height: size - - Image { - id: icon - anchors.fill: root - anchors.margins: padding - source: path - sourceSize: Qt.size(size, size) - fillMode: Image.PreserveAspectFit - smooth: true - antialiasing: true - asynchronous: true - } - - MultiEffect { - id: overlay - anchors.fill: icon - source: icon - colorization: 1.0 - brightness: 1.0 - } - - HoverHandler { - id: mouse - acceptedDevices: PointerDevice.Mouse - } -} diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/MyMenu.qml b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/MyMenu.qml index 99795b5e..1f1d30c5 100644 --- a/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/MyMenu.qml +++ b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/MyMenu.qml @@ -8,35 +8,38 @@ import FileSystemModule Menu { id: root - background: Rectangle { - implicitWidth: 200 - implicitHeight: 40 - color: Colors.surface2 - } - delegate: MenuItem { id: menuItem - implicitWidth: 200 - implicitHeight: 40 contentItem: Item { Text { anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left anchors.leftMargin: 5 + text: menuItem.text color: enabled ? Colors.text : Colors.disabledText } Rectangle { + id: indicator + anchors.verticalCenter: parent.verticalCenter anchors.right: parent.right width: 6 height: parent.height + visible: menuItem.highlighted color: Colors.color2 } } background: Rectangle { + implicitWidth: 210 + implicitHeight: 35 color: menuItem.highlighted ? Colors.active : "transparent" } } + background: Rectangle { + implicitWidth: 210 + implicitHeight: 35 + color: Colors.surface2 + } } diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/MyMenuBar.qml b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/MyMenuBar.qml index a2a3fea8..4874a2c0 100644 --- a/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/MyMenuBar.qml +++ b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/MyMenuBar.qml @@ -6,130 +6,172 @@ import QtQuick.Layouts import QtQuick.Controls.Basic import FileSystemModule -// The MenuBar also serves as a controller for our Window as we don't use any decorations. +// The MenuBar also serves as a controller for our window as we don't use any decorations. MenuBar { id: root - required property ApplicationWindow rootWindow + required property ApplicationWindow dragWindow property alias infoText: windowInfo.text - implicitHeight: 25 - - // The top level menus on the left side + // Customization of the top level menus inside the MenuBar delegate: MenuBarItem { id: menuBarItem - implicitHeight: 25 contentItem: Text { horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter - color: menuBarItem.highlighted ? Colors.textFile : Colors.text - opacity: enabled ? 1.0 : 0.3 + text: menuBarItem.text - elide: Text.ElideRight font: menuBarItem.font + elide: Text.ElideRight + color: menuBarItem.highlighted ? Colors.textFile : Colors.text + opacity: enabled ? 1.0 : 0.3 } background: Rectangle { + id: background + color: menuBarItem.highlighted ? Colors.selection : "transparent" Rectangle { id: indicator + width: 0; height: 3 anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: parent.bottom - color: Colors.color1 + color: Colors.color1 states: State { - name: "active"; when: menuBarItem.highlighted - PropertyChanges { target: indicator; width: parent.width } + name: "active" + when: menuBarItem.highlighted + PropertyChanges { + indicator.width: background.width - 2 + } } - transitions: Transition { NumberAnimation { properties: "width" - duration: 300 + duration: 175 } } - } } } + // We use the contentItem property as a place to attach our window decorations. Beneath + // the usual menu entries within a MenuBar, it includes a centered information text, along + // with the minimize, maximize, and close buttons. + contentItem: RowLayout { + id: windowBar - // The background property contains an information text in the middle as well as the - // Minimize, Maximize and Close Buttons. - background: Rectangle { - color: Colors.surface2 - // Make the empty space drag the specified root window. - WindowDragHandler { dragWindow: rootWindow } + Layout.fillWidth: true + Layout.fillHeight: true + + spacing: root.spacing + Repeater { + id: menuBarItems - Text { - id: windowInfo - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - color: Colors.text + Layout.alignment: Qt.AlignLeft + model: root.contentModel } - component InteractionButton: Rectangle { - signal action; - property alias hovered: hoverHandler.hovered + Item { + Layout.fillWidth: true + Layout.fillHeight: true + Text { + id: windowInfo + + width: parent.width; height: parent.height + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + leftPadding: windowActions.width + color: Colors.text + clip: true + } + } - width: root.height - anchors.top: parent.top - anchors.bottom: parent.bottom - color: hovered ? Colors.background : "transparent" + RowLayout { + id: windowActions - HoverHandler { id: hoverHandler } - TapHandler { onTapped: action() } - } + Layout.alignment: Qt.AlignRight + Layout.fillHeight: true - InteractionButton { - id: minimize + spacing: 0 - anchors.right: maximize.left - onAction: rootWindow.showMinimized() - Rectangle { - width: parent.height - 10; height: 2 - anchors.centerIn: parent - color: parent.hovered ? Colors.iconIndicator : Colors.icon + component InteractionButton: Rectangle { + id: interactionButton + + signal action() + property alias hovered: hoverHandler.hovered + + Layout.fillHeight: true + Layout.preferredWidth: height + + color: hovered ? Colors.background : "transparent" + HoverHandler { + id: hoverHandler + } + TapHandler { + id: tapHandler + onTapped: interactionButton.action() + } } - } - InteractionButton { - id: maximize + InteractionButton { + id: minimize - anchors.right: close.left - onAction: rootWindow.showMaximized() - Rectangle { - anchors.fill: parent - anchors.margins: 5 - border.width: 2 - color: "transparent" - border.color: parent.hovered ? Colors.iconIndicator : Colors.icon + onAction: root.dragWindow.showMinimized() + Rectangle { + anchors.centerIn: parent + color: parent.hovered ? Colors.iconIndicator : Colors.icon + height: 2 + width: parent.height - 14 + } } - } - InteractionButton { - id: close + InteractionButton { + id: maximize - color: hovered ? "#ec4143" : "transparent" - anchors.right: parent.right - onAction: rootWindow.close() - Rectangle { - width: parent.height - 8; height: 2 - anchors.centerIn: parent - color: parent.hovered ? Colors.iconIndicator : Colors.icon - rotation: 45 - transformOrigin: Item.Center - antialiasing: true + onAction: root.dragWindow.showMaximized() + Rectangle { + anchors.fill: parent + anchors.margins: 7 + border.color: parent.hovered ? Colors.iconIndicator : Colors.icon + border.width: 2 + color: "transparent" + } + } + + InteractionButton { + id: close + + color: hovered ? "#ec4143" : "transparent" + onAction: root.dragWindow.close() Rectangle { - width: parent.height - height: parent.width anchors.centerIn: parent - color: parent.color + width: parent.height - 8; height: 2 + + rotation: 45 antialiasing: true + transformOrigin: Item.Center + color: parent.hovered ? Colors.iconIndicator : Colors.icon + + Rectangle { + anchors.centerIn: parent + width: parent.height + height: parent.width + + antialiasing: true + color: parent.color + } } } } } + background: Rectangle { + color: Colors.surface2 + // Make the empty space drag the specified root window. + WindowDragHandler { + dragWindow: root.dragWindow + } + } } diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/ResizeButton.qml b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/ResizeButton.qml index eb2e5bc0..0df65bf8 100644 --- a/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/ResizeButton.qml +++ b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/ResizeButton.qml @@ -5,6 +5,8 @@ import QtQuick.Controls import FileSystemModule Button { + required property ApplicationWindow resizeWindow + icon.width: 20; icon.height: 20 anchors.right: parent.right anchors.bottom: parent.bottom @@ -12,12 +14,10 @@ Button { bottomPadding: 3 icon.source: "../icons/resize.svg" - icon.color: down || checked ? Colors.iconIndicator : Colors.icon + icon.color: hovered ? Colors.iconIndicator : Colors.icon + background: null checkable: false display: AbstractButton.IconOnly - background: null - onPressed: { - root.startSystemResize(Qt.BottomEdge | Qt.RightEdge) - } + onPressed: resizeWindow.startSystemResize(Qt.BottomEdge | Qt.RightEdge) } diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/Sidebar.qml b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/Sidebar.qml index 29062a9b..04880a55 100644 --- a/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/Sidebar.qml +++ b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/Sidebar.qml @@ -8,78 +8,94 @@ import FileSystemModule Rectangle { id: root + + property alias currentTabIndex: topBar.currentIndex + required property ApplicationWindow dragWindow + readonly property int tabBarSpacing: 10 + color: Colors.surface2 - required property ApplicationWindow rootWindow - property alias currentTabIndex: tabBar.currentIndex + component SidebarEntry: Button { + id: sidebarButton - ColumnLayout { - anchors.fill: root - anchors.topMargin: 10 - anchors.bottomMargin: 10 - spacing: 10 + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true - // TabBar is designed to be horizontal, whereas we need a vertical bar. - // We can easily achieve that by using a Container. - Container { - id: tabBar + icon.color: down || checked ? Colors.iconIndicator : Colors.icon + icon.width: 27 + icon.height: 27 - Layout.fillWidth: true + topPadding: 0 + rightPadding: 0 + bottomPadding: 0 + leftPadding: 0 + background: null - // ButtonGroup ensures that only one button can be checked at a time. - ButtonGroup { - buttons: tabBar.contentItem.children - // We have to manage the currentIndex ourselves, which we do by setting it to the - // index of the currently checked button. - // We use setCurrentIndex instead of setting the currentIndex property to avoid breaking bindings. - // See "Managing the Current Index" in Container's documentation for more information. - onCheckedButtonChanged: tabBar.setCurrentIndex(Math.max(0, buttons.indexOf(checkedButton))) - } + Rectangle { + id: indicator - contentItem: ColumnLayout { - spacing: tabBar.spacing + anchors.verticalCenter: parent.verticalCenter + x: 2 + width: 4 + height: sidebarButton.icon.height * 1.2 - Repeater { - model: tabBar.contentModel - } - } + visible: sidebarButton.checked + color: Colors.color1 + } + } + + // TabBar is designed to be horizontal, whereas we need a vertical bar. + // We can easily achieve that by using a Container. + component TabBar: Container { + id: tabBarComponent + + Layout.fillWidth: true + // ButtonGroup ensures that only one button can be checked at a time. + ButtonGroup { + buttons: tabBarComponent.contentChildren + + // We have to manage the currentIndex ourselves, which we do by setting it to the index + // of the currently checked button. We use setCurrentIndex instead of setting the + // currentIndex property to avoid breaking bindings. See "Managing the Current Index" + // in Container's documentation for more information. + onCheckedButtonChanged: tabBarComponent.setCurrentIndex( + Math.max(0, buttons.indexOf(checkedButton))) + } - component SidebarEntry: Button { - id: sidebarButton - icon.color: down || checked ? Colors.iconIndicator : Colors.icon - icon.width: 35 - icon.height: 35 - leftPadding: 8 + indicator.width - - background: null - - Rectangle { - id: indicator - x: 4 - anchors.verticalCenter: parent.verticalCenter - width: 4 - height: sidebarButton.icon.width - color: Colors.color1 - visible: sidebarButton.checked - } + contentItem: ColumnLayout { + spacing: tabBarComponent.spacing + Repeater { + model: tabBarComponent.contentModel } + } + } + ColumnLayout { + anchors.fill: root + anchors.topMargin: root.tabBarSpacing + anchors.bottomMargin: root.tabBarSpacing + + spacing: root.tabBarSpacing + TabBar { + id: topBar + + spacing: root.tabBarSpacing // Shows help text when clicked. SidebarEntry { + id: infoTab icon.source: "../icons/light_bulb.svg" checkable: true checked: true - - Layout.alignment: Qt.AlignHCenter } // Shows the file system when clicked. SidebarEntry { + id: filesystemTab + icon.source: "../icons/read.svg" checkable: true - - Layout.alignment: Qt.AlignHCenter } + // Shows the scheme switcher SidebarEntry { icon.source: "../icons/leaf.svg" @@ -95,25 +111,31 @@ Rectangle { Layout.fillWidth: true // Make the empty space drag our main window. - WindowDragHandler { dragWindow: rootWindow } + WindowDragHandler { + dragWindow: root.dragWindow + } } - // Opens the Qt website in the system's web browser. - SidebarEntry { - id: qtWebsiteButton - icon.source: "../icons/globe.svg" - checkable: false + TabBar { + id: bottomBar - onClicked: Qt.openUrlExternally("https://www.qt.io/") - } + spacing: root.tabBarSpacing + // Opens the Qt website in the system's web browser. + SidebarEntry { + id: qtWebsiteButton + icon.source: "../icons/globe.svg" + checkable: false + onClicked: Qt.openUrlExternally("https://www.qt.io/") + } - // Opens the About Qt Window. - SidebarEntry { - id: aboutQtButton - icon.source: "../icons/info_sign.svg" - checkable: false + // Opens the About Qt Window. + SidebarEntry { + id: aboutQtButton - onClicked: aboutQtWindow.visible = !aboutQtWindow.visible + icon.source: "../icons/info_sign.svg" + checkable: false + onClicked: aboutQtWindow.visible = !aboutQtWindow.visible + } } } diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qmldir b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qmldir index 6c9e3ed9..e9c99b07 100644 --- a/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qmldir +++ b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qmldir @@ -1,7 +1,7 @@ module FileSystemModule Main 1.0 Main.qml -Icon 1.0 qml/Icon.qml About 1.0 qml/About.qml +Editor 1.0 qml/Editor.qml MyMenu 1.0 qml/MyMenu.qml Sidebar 1.0 qml/Sidebar.qml MyMenuBar 1.0 qml/MyMenuBar.qml diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/editormodels.py b/sources/pyside6/doc/tutorials/extendedexplorer/editormodels.py new file mode 100644 index 00000000..68814772 --- /dev/null +++ b/sources/pyside6/doc/tutorials/extendedexplorer/editormodels.py @@ -0,0 +1,116 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from PySide6.QtWidgets import QFileSystemModel +from PySide6.QtQuick import QQuickTextDocument +from PySide6.QtQml import QmlElement, QmlSingleton +from PySide6.QtCore import (Qt, QDir, QAbstractListModel, Slot, QFile, QTextStream, + QMimeDatabase, QFileInfo, QStandardPaths, QModelIndex, + Signal, Property) + +QML_IMPORT_NAME = "FileSystemModule" +QML_IMPORT_MAJOR_VERSION = 1 + + +@QmlElement +@QmlSingleton +class FileSystemModel(QFileSystemModel): + + rootIndexChanged = Signal() + + def getDefaultRootDir(): + return QStandardPaths.writableLocation(QStandardPaths.StandardLocation.HomeLocation) + + def __init__(self, parent=None): + super().__init__(parent=parent) + self.mRootIndex = QModelIndex() + self.mDb = QMimeDatabase() + self.setFilter(QDir.Filter.AllEntries | QDir.Filter.Hidden | QDir.Filter.NoDotAndDotDot) + self.setInitialDirectory() + + # check for the correct mime type and then read the file. + # returns the text file's content or an error message on failure + @Slot(str, result=str) + def readFile(self, path): + if path == "": + return "" + + file = QFile(path) + + mime = self.mDb.mimeTypeForFile(QFileInfo(file)) + if ('text' in mime.comment().lower() + or any('text' in s.lower() for s in mime.parentMimeTypes())): + if file.open(QFile.OpenModeFlag.ReadOnly | QFile.OpenModeFlag.Text): + stream = QTextStream(file).readAll() + file.close() + return stream + else: + return self.tr("Error opening the file!") + return self.tr("File type not supported!") + + @Slot(QQuickTextDocument, int, result=int) + def currentLineNumber(self, textDocument, cursorPosition): + td = textDocument.textDocument() + tb = td.findBlock(cursorPosition) + return tb.blockNumber() + + def setInitialDirectory(self, path=getDefaultRootDir()): + dir = QDir(path) + if dir.makeAbsolute(): + self.setRootPath(dir.path()) + else: + self.setRootPath(self.getDefaultRootDir()) + self.setRootIndex(self.index(dir.path())) + + # we only need one column in this example + def columnCount(self, parent): + return 1 + + @Property(QModelIndex, notify=rootIndexChanged) + def rootIndex(self): + return self.mRootIndex + + def setRootIndex(self, index): + if (index == self.mRootIndex): + return + self.mRootIndex = index + self.rootIndexChanged.emit() + + +@QmlElement +class LineNumberModel(QAbstractListModel): + + lineCountChanged = Signal() + + def __init__(self, parent=None): + self.mLineCount = 0 + super().__init__(parent=parent) + + @Property(int, notify=lineCountChanged) + def lineCount(self): + return self.mLineCount + + @lineCount.setter + def lineCount(self, n): + if n < 0: + print("lineCount must be greater then zero") + return + if self.mLineCount == n: + return + + if self.mLineCount < n: + self.beginInsertRows(QModelIndex(), self.mLineCount, n - 1) + self.mLineCount = n + self.endInsertRows() + else: + self.beginRemoveRows(QModelIndex(), n, self.mLineCount - 1) + self.mLineCount = n + self.endRemoveRows() + + def rowCount(self, parent): + return self.mLineCount + + def data(self, index, role): + if not self.checkIndex(index) or role != Qt.ItemDataRole.DisplayRole: + return + return index.row() diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/extendedexplorer.md b/sources/pyside6/doc/tutorials/extendedexplorer/extendedexplorer.md index 7e265a7e..0ac7bec1 100644 --- a/sources/pyside6/doc/tutorials/extendedexplorer/extendedexplorer.md +++ b/sources/pyside6/doc/tutorials/extendedexplorer/extendedexplorer.md @@ -151,7 +151,7 @@ by adding a new entry to the `Sidebar.qml` file. language: QML caption: true linenos: true -lines: 83-90 +lines: 99-105 --- ``` @@ -164,7 +164,7 @@ modified. The necessary changes will be made to the Main.qml file: language: QML caption: true linenos: true -lines: 169-198 +lines: 170-187 --- ``` @@ -178,7 +178,7 @@ will also be made to the Main.qml file. language: QML caption: true linenos: true -lines: 144-145 +lines: 147-150 --- ``` diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/extendedexplorer.py b/sources/pyside6/doc/tutorials/extendedexplorer/extendedexplorer.py deleted file mode 100644 index 6ad6e926..00000000 --- a/sources/pyside6/doc/tutorials/extendedexplorer/extendedexplorer.py +++ /dev/null @@ -1,70 +0,0 @@ -# Copyright (C) 2023 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -""" -This example shows how to customize Qt Quick Controls by implementing a simple filesystem explorer. -""" - -# Compile both resource files app.qrc and icons.qrc and include them here if you wish -# to load them from the resource system. Currently, all resources are loaded locally -# import FileSystemModule.rc_icons -# import FileSystemModule.rc_app -from scheme_manager import SchemeManager - -from PySide6.QtWidgets import QFileSystemModel -from PySide6.QtGui import QGuiApplication -from PySide6.QtQml import (QQmlApplicationEngine, QmlElement, QmlSingleton) -from PySide6.QtCore import (Slot, QFile, QTextStream, QMimeDatabase, QFileInfo, QStandardPaths) - -import sys - - -QML_IMPORT_NAME = "FileSystemModule" -QML_IMPORT_MAJOR_VERSION = 1 - - -@QmlElement -@QmlSingleton -class FileSystemModel(QFileSystemModel): - def __init__(self, parent=None): - super().__init__(parent=parent) - self.setRootPath(QStandardPaths.writableLocation(QStandardPaths.HomeLocation)) - self.db = QMimeDatabase() - - # we only need one column in this example - def columnCount(self, parent): - return 1 - - # check for the correct mime type and then read the file. - # returns the text file's content or an error message on failure - @Slot(str, result=str) - def readFile(self, path): - if path == "": - return "" - - file = QFile(path) - - mime = self.db.mimeTypeForFile(QFileInfo(file)) - if 'text' in mime.comment().lower() or any('text' in s.lower() for s in mime.parentMimeTypes()): - if file.open(QFile.ReadOnly | QFile.Text): - stream = QTextStream(file).readAll() - return stream - else: - return self.tr("Error opening the file!") - return self.tr("File type not supported!") - - -if __name__ == '__main__': - app = QGuiApplication(sys.argv) - app.setOrganizationName("QtProject") - app.setApplicationName("File System Explorer") - engine = QQmlApplicationEngine() - # Include the path of this file to search for the 'qmldir' module - engine.addImportPath(sys.path[0]) - - engine.loadFromModule("FileSystemModule", "Main") - - if not engine.rootObjects(): - sys.exit(-1) - - sys.exit(app.exec()) diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/extendedexplorer.pyproject b/sources/pyside6/doc/tutorials/extendedexplorer/extendedexplorer.pyproject index f6f2e4e8..77a3969a 100644 --- a/sources/pyside6/doc/tutorials/extendedexplorer/extendedexplorer.pyproject +++ b/sources/pyside6/doc/tutorials/extendedexplorer/extendedexplorer.pyproject @@ -1,21 +1,23 @@ { "files": [ - "extendedexplorer.py", - "color_scheme.py", + "main.py", + "editormodels.py", + "scheme_manager.py", + "schemes.json", "FileSystemModule/qmldir", "FileSystemModule/app.qrc", "FileSystemModule/icons.qrc", - "FileSystemModule/qmldir", "FileSystemModule/Main.qml", "FileSystemModule/qml/About.qml", "FileSystemModule/qml/ColorScheme.qml", + "FileSystemModule/qml/Editor.qml", "FileSystemModule/qml/FileSystemView.qml", - "FileSystemModule/qml/Icon.qml", "FileSystemModule/qml/MyMenu.qml", "FileSystemModule/qml/MyMenuBar.qml", "FileSystemModule/qml/ResizeButton.qml", "FileSystemModule/qml/Sidebar.qml", "FileSystemModule/qml/WindowDragHandler.qml", + "FileSystemModule/icons/app_icon.svg", "FileSystemModule/icons/folder_closed.svg", "FileSystemModule/icons/folder_open.svg", "FileSystemModule/icons/generic_file.svg", diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/main.py b/sources/pyside6/doc/tutorials/extendedexplorer/main.py new file mode 100644 index 00000000..f1e6e7d9 --- /dev/null +++ b/sources/pyside6/doc/tutorials/extendedexplorer/main.py @@ -0,0 +1,50 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +""" +This example shows how to customize Qt Quick Controls by implementing a simple filesystem explorer. +""" + +# Compile both resource files app.qrc and icons.qrc and include them here if you wish +# to load them from the resource system. Currently, all resources are loaded locally +# import FileSystemModule.rc_icons +# import FileSystemModule.rc_app + +from scheme_manager import SchemeManager +from editormodels import FileSystemModel +import PySide6 +from PySide6.QtGui import QGuiApplication, QIcon +from PySide6.QtQml import QQmlApplicationEngine +from PySide6.QtCore import QCommandLineParser + +import sys + +if __name__ == '__main__': + app = QGuiApplication(sys.argv) + app.setOrganizationName("QtProject") + app.setApplicationName("File System Explorer") + app.setApplicationVersion(PySide6.__version__) + app.setWindowIcon(QIcon("FileSystemModule/icons/app_icon.svg")) + + parser = QCommandLineParser() + parser.setApplicationDescription("Qt Filesystemexplorer Example") + parser.addHelpOption() + parser.addVersionOption() + parser.addPositionalArgument("", "Initial directory", "[path]") + parser.process(app) + args = parser.positionalArguments() + + engine = QQmlApplicationEngine() + # Include the path of this file to search for the 'qmldir' module + engine.addImportPath(sys.path[0]) + + engine.loadFromModule("FileSystemModule", "Main") + + if not engine.rootObjects(): + sys.exit(-1) + + if (len(args) == 1): + fsm = engine.singletonInstance("FileSystemModule", "FileSystemModel") + fsm.setInitialDirectory(args[0]) + + sys.exit(app.exec()) diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/resources/extendedexplorer.webp b/sources/pyside6/doc/tutorials/extendedexplorer/resources/extendedexplorer.webp index 1f4e2cfa..79a14f34 100644 Binary files a/sources/pyside6/doc/tutorials/extendedexplorer/resources/extendedexplorer.webp and b/sources/pyside6/doc/tutorials/extendedexplorer/resources/extendedexplorer.webp differ diff --git a/sources/pyside6/doc/tutorials/qmlapp/qmlapplication.rst b/sources/pyside6/doc/tutorials/qmlapp/qmlapplication.rst index b77648d6..c6d72e74 100644 --- a/sources/pyside6/doc/tutorials/qmlapp/qmlapplication.rst +++ b/sources/pyside6/doc/tutorials/qmlapp/qmlapplication.rst @@ -15,15 +15,15 @@ defined in the QML file. Before you begin, install the following prerequisites: * The `PySide6 `_ Python packages. -* Qt Creator v4.9 beta1 or later from +* *Qt Creator* from `https://download.qt.io `_. The following step-by-step instructions guide you through application -development process using Qt Creator: +development process using *Qt Creator*: -#. Open Qt Creator and select **File > New File or Project..** menu item +#. Open *Qt Creator* and select **File > New File or Project..** menu item to open following dialog: .. image:: newpyproject.png diff --git a/sources/pyside6/libpyside/CMakeLists.txt b/sources/pyside6/libpyside/CMakeLists.txt index e9fd0d56..ebfe123d 100644 --- a/sources/pyside6/libpyside/CMakeLists.txt +++ b/sources/pyside6/libpyside/CMakeLists.txt @@ -8,7 +8,6 @@ set(libpyside_libraries Qt::Core Qt::CorePrivate) set(CMAKE_AUTOMOC ON) set(libpyside_HEADERS # installed below - pysideqslotobject_p.h class_property.h dynamicqmetaobject.h feature_select.h @@ -33,6 +32,7 @@ set(libpyside_HEADERS # installed below pysideqhash.h pysideqmetatype.h pysideqobject.h + pysideqslotobject_p.h pysidesignal.h pysidesignal_p.h pysideslot_p.h @@ -44,7 +44,6 @@ set(libpyside_HEADERS # installed below ) set(libpyside_SRC - pysideqslotobject_p.cpp class_property.cpp dynamicqmetaobject.cpp feature_select.cpp @@ -53,6 +52,7 @@ set(libpyside_SRC pysideclassdecorator.cpp pysideclassinfo.cpp pysideqenum.cpp + pysideqslotobject_p.cpp pysidemetafunction.cpp pysidesignal.cpp pysideslot.cpp diff --git a/sources/pyside6/libpyside/class_property.cpp b/sources/pyside6/libpyside/class_property.cpp index 99104445..2bed97ef 100644 --- a/sources/pyside6/libpyside/class_property.cpp +++ b/sources/pyside6/libpyside/class_property.cpp @@ -5,6 +5,7 @@ #include "pysidestaticstrings.h" #include "feature_select.h" +#include #include #include @@ -23,14 +24,14 @@ extern "C" { // `class_property.__get__()`: Always pass the class instead of the instance. static PyObject *PyClassProperty_descr_get(PyObject *self, PyObject * /*ob*/, PyObject *cls) { - return PyProperty_Type.tp_descr_get(self, cls, cls); + return PepExt_Type_GetDescrGetSlot(&PyProperty_Type)(self, cls, cls); } // `class_property.__set__()`: Just like the above `__get__()`. static int PyClassProperty_descr_set(PyObject *self, PyObject *obj, PyObject *value) { PyObject *cls = PyType_Check(obj) ? obj : reinterpret_cast(Py_TYPE(obj)); - return PyProperty_Type.tp_descr_set(self, cls, value); + return PepExt_Type_GetDescrSetSlot(&PyProperty_Type)(self, cls, value); } // PYSIDE-2230: Why is this metaclass necessary? @@ -80,7 +81,7 @@ static int PyClassProperty_tp_init(PyObject *self, PyObject *args, PyObject *kwa { auto hold = Py_TYPE(self); self->ob_type = &PyProperty_Type; - auto ret = PyProperty_Type.tp_init(self, args, kwargs); + auto ret = PepExt_Type_GetInitSlot(&PyProperty_Type)(self, args, kwargs); self->ob_type = hold; return ret; } @@ -138,9 +139,9 @@ static int SbkObjectType_meta_setattro(PyObject *obj, PyObject *name, PyObject * && !PyObject_IsInstance(value, class_prop); if (call_descr_set) { // Call `class_property.__set__()` instead of replacing the `class_property`. - return Py_TYPE(descr)->tp_descr_set(descr, obj, value); + return PepExt_Type_GetDescrSetSlot(Py_TYPE(descr))(descr, obj, value); } // Replace existing attribute. - return PyType_Type.tp_setattro(obj, name, value); + return PepExt_Type_GetSetAttroSlot(&PyType_Type)(obj, name, value); } } // extern "C" diff --git a/sources/pyside6/libpyside/dynamicqmetaobject.cpp b/sources/pyside6/libpyside/dynamicqmetaobject.cpp index b41dbc27..048001f8 100644 --- a/sources/pyside6/libpyside/dynamicqmetaobject.cpp +++ b/sources/pyside6/libpyside/dynamicqmetaobject.cpp @@ -634,7 +634,7 @@ void MetaObjectBuilderPrivate::parsePythonType(PyTypeObject *type) const int index = m_baseObject->indexOfProperty(name); if (index == -1) addProperty(name, value); - } else if (Py_TYPE(value)->tp_call != nullptr) { + } else if (PepType_GetSlot(Py_TYPE(value), Py_tp_call) != nullptr) { // PYSIDE-198: PyFunction_Check does not work with Nuitka. // Register slots. if (PyObject_HasAttr(value, slotAttrName)) { diff --git a/sources/pyside6/libpyside/feature_select.cpp b/sources/pyside6/libpyside/feature_select.cpp index 3f8e3887..cfd46526 100644 --- a/sources/pyside6/libpyside/feature_select.cpp +++ b/sources/pyside6/libpyside/feature_select.cpp @@ -107,7 +107,7 @@ createDerivedDictType() PyObject *ChameleonDict = PepRun_GetResult(R"CPP(if True: class ChameleonDict(dict): - __slots__ = ("dict_ring", "select_id") + __slots__ = ("dict_ring", "select_id", "orig_dict") result = ChameleonDict @@ -172,9 +172,9 @@ static bool replaceClassDict(PyTypeObject *type) // insert the dict into itself as ring setNextDict(new_dict, new_dict); // We have now an exact copy of the dict with a new type. - // Replace `__dict__` which usually has refcount 1 (but see cyclic_test.py) - Py_DECREF(PepType_GetDict(type)); PepType_SetDict(type, new_dict); + // PYSIDE-2404: Retain the original dict for easy late init. + PyObject_SetAttr(new_dict, PySideName::orig_dict(), dict); return true; } @@ -185,6 +185,7 @@ static bool addNewDict(PyTypeObject *type, int select_id) * A 'false' return is fatal. */ AutoDecRef dict(PepType_GetDict(type)); + AutoDecRef orig_dict(PyObject_GetAttr(dict, PySideName::orig_dict())); auto *ob_ndt = reinterpret_cast(new_dict_type); auto *new_dict = PyObject_CallObject(ob_ndt, nullptr); if (new_dict == nullptr) @@ -195,6 +196,8 @@ static bool addNewDict(PyTypeObject *type, int select_id) setNextDict(dict, new_dict); setNextDict(new_dict, next_dict); PepType_SetDict(type, new_dict); + // PYSIDE-2404: Retain the original dict for easy late init. + PyObject_SetAttr(new_dict, PySideName::orig_dict(), orig_dict); return true; } @@ -393,12 +396,18 @@ static FeatureProc featureProcArray[] = { static bool patch_property_impl(); static bool is_initialized = false; +static void featureEnableCallback(bool enable) +{ + featurePointer = enable ? featureProcArray : nullptr; +} + void init() { // This function can be called multiple times. if (!is_initialized) { featurePointer = featureProcArray; initSelectableFeature(SelectFeatureSet); + setSelectableFeatureCallback(featureEnableCallback); patch_property_impl(); is_initialized = true; } diff --git a/sources/pyside6/libpyside/globalreceiverv2.cpp b/sources/pyside6/libpyside/globalreceiverv2.cpp index 09385645..51070b4a 100644 --- a/sources/pyside6/libpyside/globalreceiverv2.cpp +++ b/sources/pyside6/libpyside/globalreceiverv2.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -135,7 +136,7 @@ PyObject *DynamicSlotDataV2::callback() //create a callback based on method data if (m_isMethod) - callback = Py_TYPE(m_callback)->tp_descr_get(m_callback, m_pythonSelf, nullptr); + callback = PepExt_Type_CallDescrGet(m_callback, m_pythonSelf, nullptr); else Py_INCREF(callback); @@ -161,7 +162,7 @@ void DynamicSlotDataV2::onCallbackDestroyed(void *data) auto self = reinterpret_cast(data); self->m_weakRef = nullptr; Py_BEGIN_ALLOW_THREADS - SignalManager::instance().deleteGobalReceiver(self->m_parent); + SignalManager::instance().deleteGlobalReceiver(self->m_parent); Py_END_ALLOW_THREADS } diff --git a/sources/pyside6/libpyside/globalreceiverv2.h b/sources/pyside6/libpyside/globalreceiverv2.h index 24911c3d..0e3bc562 100644 --- a/sources/pyside6/libpyside/globalreceiverv2.h +++ b/sources/pyside6/libpyside/globalreceiverv2.h @@ -8,6 +8,7 @@ #include "dynamicqmetaobject.h" +#include #include #include #include @@ -33,18 +34,14 @@ struct GlobalReceiverKey { return qHashMulti(seed, k.object, k.method); } + friend constexpr bool comparesEqual(const GlobalReceiverKey &lhs, + const GlobalReceiverKey &rhs) noexcept + { + return lhs.object == rhs.object && lhs.method == rhs.method; + } + Q_DECLARE_EQUALITY_COMPARABLE_LITERAL_TYPE(GlobalReceiverKey) }; -inline bool operator==(const GlobalReceiverKey &k1, const GlobalReceiverKey &k2) -{ - return k1.object == k2.object && k1.method == k2.method; -} - -inline bool operator!=(const GlobalReceiverKey &k1, const GlobalReceiverKey &k2) -{ - return k1.object != k2.object || k1.method != k2.method; -} - /// A class used to link C++ Signals to non C++ slots (Python callbacks) by /// providing fake slots for QObject::connect(). /// It keeps a Python callback and the list of QObject senders. It is stored diff --git a/sources/pyside6/libpyside/pyside.cpp b/sources/pyside6/libpyside/pyside.cpp index 572341e8..3a06a03e 100644 --- a/sources/pyside6/libpyside/pyside.cpp +++ b/sources/pyside6/libpyside/pyside.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -42,6 +43,7 @@ #include #include #include +#include #include #include @@ -62,6 +64,20 @@ QT_END_NAMESPACE Q_LOGGING_CATEGORY(lcPySide, "qt.pyside.libpyside", QtCriticalMsg) +static QObjectData *qt_object_private(const QObject *o) +{ + class FriendlyQObject : public QObject { + public: + using QObject::d_ptr; + }; + return static_cast(o)->d_ptr.data(); +} + +static bool hasDynamicMetaObject(const QObject *o) +{ + return qt_object_private(o)->metaObject != nullptr; +} + namespace PySide { @@ -446,6 +462,8 @@ void initDynamicMetaObject(PyTypeObject *type, const QMetaObject *base, std::siz TypeUserData *retrieveTypeUserData(PyTypeObject *pyTypeObj) { + if (!SbkObjectType_Check(pyTypeObj)) + return nullptr; return reinterpret_cast(Shiboken::ObjectType::getTypeUserData(pyTypeObj)); } @@ -672,6 +690,32 @@ static void invalidatePtr(any_t *object) static const char invalidatePropertyName[] = "_PySideInvalidatePtr"; +// PYSIDE-2749: Skip over internal QML classes and classes +// with dynamic meta objects when looking for the best matching +// type to avoid unnessarily triggering the lazy load mechanism +// for classes that do not have a binding from things like eventFilter(). +static inline bool isInternalObject(const char *name) +{ + return std::strstr(name, "QMLTYPE") != nullptr || std::strstr(name, "QQmlPrivate") != nullptr; +} + +static const QMetaObject *metaObjectCandidate(const QObject *o) +{ + auto *metaObject = o->metaObject(); + // Skip QML helper types and Python objects + if (hasDynamicMetaObject(o)) { + if (auto *super = metaObject->superClass()) + metaObject = super; + } + for (auto *candidate = metaObject; candidate != nullptr; candidate = candidate->superClass()) { + if (!isInternalObject(candidate->className())) { + metaObject = candidate; + break; + } + } + return metaObject; +} + // PYSIDE-1214, when creating new wrappers for classes inheriting QObject but // not exposed to Python, try to find the best-matching (most-derived) Qt // class by walking up the meta objects. @@ -679,7 +723,8 @@ static const char *typeName(const QObject *cppSelf) { const char *typeName = typeid(*cppSelf).name(); if (!Shiboken::Conversions::getConverter(typeName)) { - for (auto metaObject = cppSelf->metaObject(); metaObject; metaObject = metaObject->superClass()) { + auto *metaObject = metaObjectCandidate(cppSelf); + for (; metaObject != nullptr; metaObject = metaObject->superClass()) { const char *name = metaObject->className(); if (Shiboken::Conversions::getConverter(name)) { typeName = name; @@ -849,7 +894,7 @@ bool registerInternalQtConf() QString executablePath = QString::fromWCharArray(Py_GetProgramFullPath()); #else // PYSIDE-535: FIXME: Add this function when available. - QString executablePath = QLatin1String("missing Py_GetProgramFullPath"); + QString executablePath = QLatin1StringView("missing Py_GetProgramFullPath"); #endif // PYPY_VERSION QString appDirPath = QFileInfo(executablePath).absolutePath(); @@ -1062,9 +1107,15 @@ static void formatPyObjectValue(PyObject *obj, QDebug &debug) { if (PyType_Check(obj) != 0) debug << "type: \"" << pyTypeName(obj) << '"'; - else if (PyLong_Check(obj) != 0) - debug << PyLong_AsLongLong(obj); - else if (PyFloat_Check(obj) != 0) + else if (PyLong_Check(obj) != 0) { + const auto llv = PyLong_AsLongLong(obj); + if (PyErr_Occurred() != PyExc_OverflowError) { + debug << llv; + } else { + PyErr_Clear(); + debug << "0x" << Qt::hex << PyLong_AsUnsignedLongLong(obj) << Qt::dec; + } + } else if (PyFloat_Check(obj) != 0) debug << PyFloat_AsDouble(obj); else if (PyUnicode_Check(obj) != 0) debug << '"' << pyStringToQString(obj) << '"'; @@ -1118,5 +1169,47 @@ QDebug operator<<(QDebug debug, const debugPyObject &o) return debug; } -} //namespace PySide +debugPyBuffer::debugPyBuffer(Py_buffer *b) noexcept : m_buffer(b) +{ +} + +static void formatPy_ssizeArray(QDebug &debug, const char *name, const Py_ssize_t *array, int len) +{ + debug << ", " << name << '='; + if (array != nullptr) { + debug << '['; + for (int i = 0; i < len; ++i) + debug << array[i] << ' '; + debug << ']'; + } else { + debug << '0'; + } +} + +PYSIDE_API QDebug operator<<(QDebug debug, const debugPyBuffer &b) +{ + QDebugStateSaver saver(debug); + debug.noquote(); + debug.nospace(); + debug << "Py_buffer("; + if (b.m_buffer != nullptr) { + debug << "obj=" << b.m_buffer->obj + << ", buf=" << b.m_buffer->buf << ", len=" << b.m_buffer->len + << ", readonly=" << b.m_buffer->readonly + << ", itemsize=" << b.m_buffer->itemsize << ", format="; + if (b.m_buffer->format != nullptr) + debug << '"' << b.m_buffer->format << '"'; + else + debug << '0'; + debug << ", ndim=" << b.m_buffer->ndim; + formatPy_ssizeArray(debug, "shape", b.m_buffer->shape, b.m_buffer->ndim); + formatPy_ssizeArray(debug, "strides", b.m_buffer->strides, b.m_buffer->ndim); + formatPy_ssizeArray(debug, "suboffsets", b.m_buffer->suboffsets, b.m_buffer->ndim); + } else { + debug << '0'; + } + debug << ')'; + return debug; +} +} // namespace PySide diff --git a/sources/pyside6/libpyside/pysideclassdecorator.cpp b/sources/pyside6/libpyside/pysideclassdecorator.cpp index 39b35025..ec69c5fe 100644 --- a/sources/pyside6/libpyside/pysideclassdecorator.cpp +++ b/sources/pyside6/libpyside/pysideclassdecorator.cpp @@ -7,7 +7,7 @@ #include "pysideqobject.h" #include -#include +#include namespace PySide::ClassDecorator { @@ -63,7 +63,7 @@ int StringDecoratorPrivate::convertToString(PyObject *self, PyObject *args) if (PyUnicode_Check(arg)) { auto *pData = DecoratorPrivate::get(self); result = 0; - Shiboken::String::toCppString(arg, &(pData->m_string)); + pData->m_string.assign(Shiboken::String::toCString(arg)); } } return result; diff --git a/sources/pyside6/libpyside/pysideclassdecorator_p.h b/sources/pyside6/libpyside/pysideclassdecorator_p.h index 3e4ad751..6068f6a2 100644 --- a/sources/pyside6/libpyside/pysideclassdecorator_p.h +++ b/sources/pyside6/libpyside/pysideclassdecorator_p.h @@ -7,8 +7,9 @@ #include #include +#include -#include +#include #include #include @@ -64,7 +65,7 @@ public: /// Init function that retrieves the string parameter using convertToString() int tp_init(PyObject *self, PyObject *args, PyObject *kwds) override; - const std::string &string() const { return m_string; } + QByteArray string() const { return m_string; } protected: /// Helper function that retrieves the string parameter @@ -74,7 +75,7 @@ protected: int convertToString(PyObject *self, PyObject *args); private: - std::string m_string; + QByteArray m_string; }; /// Base class for private objects of class decorator with a type parameter @@ -119,7 +120,7 @@ struct Methods { static PyObject *tp_new(PyTypeObject *subtype) { - auto *result = reinterpret_cast(subtype->tp_alloc(subtype, 0)); + auto *result = PepExt_TypeCallAlloc(subtype, 0); result->d = new DecoratorPrivate; return reinterpret_cast(result); } @@ -129,7 +130,7 @@ struct Methods auto pySelf = reinterpret_cast(self); auto decorator = reinterpret_cast(self); delete decorator->d; - Py_TYPE(pySelf)->tp_base->tp_free(self); + PepExt_TypeCallFree(Py_TYPE(pySelf)->tp_base, self); } static PyObject *tp_call(PyObject *self, PyObject *args, PyObject *kwds) diff --git a/sources/pyside6/libpyside/pysideclassinfo.cpp b/sources/pyside6/libpyside/pysideclassinfo.cpp index 2cecb099..698cb1c7 100644 --- a/sources/pyside6/libpyside/pysideclassinfo.cpp +++ b/sources/pyside6/libpyside/pysideclassinfo.cpp @@ -51,25 +51,14 @@ PyObject *ClassInfoPrivate::tp_call(PyObject *self, PyObject *args, PyObject * / auto *pData = DecoratorPrivate::get(self); - if (pData->m_alreadyWrapped) { - PyErr_SetString(PyExc_TypeError, "This instance of ClassInfo() was already used to wrap an object"); - return nullptr; - } - - bool validClass = false; + if (pData->m_alreadyWrapped) + return PyErr_Format(PyExc_TypeError, "This instance of ClassInfo() was already used to wrap an object"); PyTypeObject *klassType = reinterpret_cast(klass); - if (auto userData = PySide::retrieveTypeUserData(klassType)) { - PySide::MetaObjectBuilder &mo = userData->mo; - mo.addInfo(pData->m_data); - pData->m_alreadyWrapped = true; - validClass = true; - } + if (!PySide::ClassInfo::setClassInfo(klassType, pData->m_data)) + return PyErr_Format(PyExc_TypeError, "This decorator can only be used on classes that are subclasses of QObject"); - if (!validClass) { - PyErr_SetString(PyExc_TypeError, "This decorator can only be used on classes that are subclasses of QObject"); - return nullptr; - } + pData->m_alreadyWrapped = true; Py_INCREF(klass); return klass; @@ -79,7 +68,7 @@ int ClassInfoPrivate::tp_init(PyObject *self, PyObject *args, PyObject *kwds) { PyObject *infoDict = nullptr; auto size = PyTuple_Size(args); - if (size == 1 && !kwds) { + if (size == 1 && kwds == nullptr) { PyObject *tmp = PyTuple_GET_ITEM(args, 0); if (PyDict_Check(tmp)) infoDict = tmp; @@ -87,7 +76,7 @@ int ClassInfoPrivate::tp_init(PyObject *self, PyObject *args, PyObject *kwds) infoDict = kwds; } - if (!infoDict) { + if (infoDict == nullptr) { PyErr_Format(PyExc_TypeError, "ClassInfo() takes either keyword argument(s) or " "a single dictionary argument"); return -1; @@ -95,15 +84,17 @@ int ClassInfoPrivate::tp_init(PyObject *self, PyObject *args, PyObject *kwds) auto *pData = DecoratorPrivate::get(self); - PyObject *key; - PyObject *value; + PyObject *key{}; + PyObject *value{}; Py_ssize_t pos = 0; // PyDict_Next causes a segfault if kwds is empty if (PyDict_Size(infoDict) > 0) { while (PyDict_Next(infoDict, &pos, &key, &value)) { if (Shiboken::String::check(key) && Shiboken::String::check(value)) { - pData->m_data[Shiboken::String::toCString(key)] = Shiboken::String::toCString(value); + ClassInfo info{Shiboken::String::toCString(key), + Shiboken::String::toCString(value)}; + pData->m_data.append(info); } else { PyErr_SetString(PyExc_TypeError, "All keys and values provided to ClassInfo() " "must be strings"); @@ -112,7 +103,7 @@ int ClassInfoPrivate::tp_init(PyObject *self, PyObject *args, PyObject *kwds) } } - return PyErr_Occurred() ? -1 : 0; + return PyErr_Occurred() != nullptr ? -1 : 0; } static const char *ClassInfo_SignatureStrings[] = { @@ -130,15 +121,38 @@ void init(PyObject *module) bool checkType(PyObject *pyObj) { - if (pyObj) - return PyType_IsSubtype(Py_TYPE(pyObj), PySideClassInfo_TypeF()); - return false; + return pyObj != nullptr + && PyType_IsSubtype(Py_TYPE(pyObj), PySideClassInfo_TypeF()) != 0; } -QMap getMap(PyObject *obj) +ClassInfoList getClassInfoList(PyObject *decorator) { - auto *pData = PySide::ClassDecorator::DecoratorPrivate::get(obj); + auto *pData = PySide::ClassDecorator::DecoratorPrivate::get(decorator); return pData->m_data; } -} //namespace PySide::Property +bool setClassInfo(PyTypeObject *type, const QByteArray &key, + const QByteArray &value) +{ + auto *userData = PySide::retrieveTypeUserData(type); + const bool result = userData != nullptr; + if (result) { + PySide::MetaObjectBuilder &mo = userData->mo; + mo.addInfo(key, value); + } + return result; +} + +bool setClassInfo(PyTypeObject *type, const ClassInfoList &list) +{ + auto *userData = PySide::retrieveTypeUserData(type); + const bool result = userData != nullptr; + if (result) { + PySide::MetaObjectBuilder &mo = userData->mo; + for (const auto &info : list) + mo.addInfo(info.key.constData(), info.value.constData()); + } + return result; +} + +} //namespace PySide::ClassInfo diff --git a/sources/pyside6/libpyside/pysideclassinfo.h b/sources/pyside6/libpyside/pysideclassinfo.h index 82580e0f..e0486582 100644 --- a/sources/pyside6/libpyside/pysideclassinfo.h +++ b/sources/pyside6/libpyside/pysideclassinfo.h @@ -8,13 +8,25 @@ #include -#include #include +#include namespace PySide::ClassInfo { +struct ClassInfo +{ + QByteArray key; + QByteArray value; +}; + +using ClassInfoList = QList; + PYSIDE_API bool checkType(PyObject* pyObj); -PYSIDE_API QMap getMap(PyObject *obj); +PYSIDE_API ClassInfoList getClassInfoList(PyObject *decorator); + +PYSIDE_API bool setClassInfo(PyTypeObject *type, const QByteArray &key, + const QByteArray &value); +PYSIDE_API bool setClassInfo(PyTypeObject *type, const ClassInfoList &list); } // namespace PySide::ClassInfo diff --git a/sources/pyside6/libpyside/pysideclassinfo_p.h b/sources/pyside6/libpyside/pysideclassinfo_p.h index eca19253..4ef456f7 100644 --- a/sources/pyside6/libpyside/pysideclassinfo_p.h +++ b/sources/pyside6/libpyside/pysideclassinfo_p.h @@ -28,7 +28,7 @@ public: int tp_init(PyObject *self, PyObject *args, PyObject *kwds) override; const char *name() const override; - QMap m_data; + ClassInfoList m_data; bool m_alreadyWrapped = false; }; diff --git a/sources/pyside6/libpyside/pysidemetafunction_p.h b/sources/pyside6/libpyside/pysidemetafunction_p.h index 953f5c91..0207ec3a 100644 --- a/sources/pyside6/libpyside/pysidemetafunction_p.h +++ b/sources/pyside6/libpyside/pysidemetafunction_p.h @@ -6,7 +6,7 @@ #include -#include +#include QT_BEGIN_NAMESPACE class QObject; diff --git a/sources/pyside6/libpyside/pysidemetatype.h b/sources/pyside6/libpyside/pysidemetatype.h index b7c53157..85e70f7c 100644 --- a/sources/pyside6/libpyside/pysidemetatype.h +++ b/sources/pyside6/libpyside/pysidemetatype.h @@ -8,7 +8,7 @@ #include -#include +#include QT_FORWARD_DECLARE_CLASS(QMetaType) diff --git a/sources/pyside6/libpyside/pysideproperty.cpp b/sources/pyside6/libpyside/pysideproperty.cpp index 2648ca43..3720815d 100644 --- a/sources/pyside6/libpyside/pysideproperty.cpp +++ b/sources/pyside6/libpyside/pysideproperty.cpp @@ -8,6 +8,7 @@ #include "pysidesignal_p.h" #include +#include #include using namespace Shiboken; @@ -179,7 +180,7 @@ void PySidePropertyPrivate::metaCall(PyObject *source, QMetaObject::Call call, v static PyObject *qpropertyTpNew(PyTypeObject *subtype, PyObject * /* args */, PyObject * /* kwds */) { - auto *me = reinterpret_cast(subtype->tp_alloc(subtype, 0)); + auto *me = PepExt_TypeCallAlloc(subtype, 0); me->d = new PySidePropertyPrivate; return reinterpret_cast(me); } @@ -258,7 +259,7 @@ static void qpropertyDeAlloc(PyObject *self) Py_DECREF(Py_TYPE(self)); } PyObject_GC_UnTrack(self); - Py_TYPE(self)->tp_free(self); + PepExt_TypeCallFree(self); } // Create a copy of the property to prevent the @property.setter from modifying @@ -474,6 +475,7 @@ static const char *Property_SignatureStrings[] = { "PySide6.QtCore.Property.read(self,fget:typing.Callable)->PySide6.QtCore.Property", "PySide6.QtCore.Property.setter(self,fset:typing.Callable)->PySide6.QtCore.Property", "PySide6.QtCore.Property.write(self,fset:typing.Callable)->PySide6.QtCore.Property", + "PySide6.QtCore.Property.__call__(self, func:typing.Callable)->PySide6.QtCore.Property", nullptr}; // Sentinel void init(PyObject *module) diff --git a/sources/pyside6/libpyside/pysideqhash.h b/sources/pyside6/libpyside/pysideqhash.h index e6e1392a..ae2d295f 100644 --- a/sources/pyside6/libpyside/pysideqhash.h +++ b/sources/pyside6/libpyside/pysideqhash.h @@ -14,7 +14,7 @@ namespace PySide /// Hash function used to enable hash on objects not supported by the native Qt /// library which have a toString() function. template -inline Py_ssize_t hash(const T& value) +[[deprecated]] inline Py_ssize_t hash(const T& value) { return qHash(value.toString()); } diff --git a/sources/pyside6/libpyside/pysideqobject.h b/sources/pyside6/libpyside/pysideqobject.h index 07453bc1..f81c5039 100644 --- a/sources/pyside6/libpyside/pysideqobject.h +++ b/sources/pyside6/libpyside/pysideqobject.h @@ -8,7 +8,9 @@ #include -#include +#include + +#include QT_FORWARD_DECLARE_CLASS(QObject) QT_FORWARD_DECLARE_STRUCT(QMetaObject) diff --git a/sources/pyside6/libpyside/pysidesignal.cpp b/sources/pyside6/libpyside/pysidesignal.cpp index f8c23b91..11e07cb0 100644 --- a/sources/pyside6/libpyside/pysidesignal.cpp +++ b/sources/pyside6/libpyside/pysidesignal.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -298,7 +299,7 @@ static void signalFree(void *vself) Py_XDECREF(self->homonymousMethod); self->homonymousMethod = nullptr; - Py_TYPE(pySelf)->tp_base->tp_free(self); + PepExt_TypeCallFree(Py_TYPE(pySelf)->tp_base, self); } static PyObject *signalGetItem(PyObject *obSelf, PyObject *key) @@ -368,7 +369,7 @@ static void signalInstanceFree(void *vself) self->d = nullptr; } self->deleted = true; - Py_TYPE(pySelf)->tp_base->tp_free(self); + PepExt_TypeCallFree(Py_TYPE(pySelf)->tp_base, self); } // PYSIDE-1523: PyFunction_Check is not accepting compiled functions and @@ -441,11 +442,35 @@ static FunctionArgumentsResult extractFunctionArgumentsFromSlot(PyObject *slot) return ret; } -static int argCount(const FunctionArgumentsResult &args) +struct ArgCount +{ + int min; + int max; +}; + +// Return a pair of minimum / arg count "foo(p1, p2=0)" -> {1, 2} +ArgCount argCount(const FunctionArgumentsResult &args) { Q_ASSERT(args.objCode); - return (PepCode_GET_FLAGS(args.objCode) & CO_VARARGS) != 0 - ? -1 : PepCode_GET_ARGCOUNT(args.objCode); + ArgCount result{-1, -1}; + if ((PepCode_GET_FLAGS(args.objCode) & CO_VARARGS) == 0) { + result.min = result.max = PepCode_GET_ARGCOUNT(args.objCode); + if (args.function != nullptr) { + if (auto *defaultArgs = PepFunction_GetDefaults(args.function)) + result.min -= PyTuple_Size(defaultArgs); + } + } + return result; +} + +// Find Signal Instance for argument count. +static PySideSignalInstance *findSignalInstance(PySideSignalInstance *source, int argCount) +{ + for (auto *si = source; si != nullptr; si = si->d->next) { + if (si->d->argCount == argCount) + return si; + } + return nullptr; } static PyObject *signalInstanceConnect(PyObject *self, PyObject *args, PyObject *kwds) @@ -459,14 +484,10 @@ static PyObject *signalInstanceConnect(PyObject *self, PyObject *args, PyObject return nullptr; PySideSignalInstance *source = reinterpret_cast(self); - if (!source->d) { - PyErr_Format(PyExc_RuntimeError, "cannot connect uninitialized SignalInstance"); - return nullptr; - } - if (source->deleted) { - PyErr_Format(PyExc_RuntimeError, "Signal source has been deleted"); - return nullptr; - } + if (!source->d) + return PyErr_Format(PyExc_RuntimeError, "cannot connect uninitialized SignalInstance"); + if (source->deleted) + return PyErr_Format(PyExc_RuntimeError, "Signal source has been deleted"); Shiboken::AutoDecRef pyArgs(PyList_New(0)); @@ -496,45 +517,32 @@ static PyObject *signalInstanceConnect(PyObject *self, PyObject *args, PyObject } } else { // Check signature of the slot (method or function) to match signal - bool matchedSlot = false; - - PySideSignalInstance *it = source; const auto args = extractFunctionArgumentsFromSlot(slot); + PySideSignalInstance *matchedSlot = nullptr; if (args.function != nullptr) { - qsizetype slotArgs = argCount(args); - if (args.isMethod) - slotArgs -= 1; + auto slotArgRange = argCount(args); + if (args.isMethod) { + slotArgRange.min -= 1; + slotArgRange.max -= 1; + } // Get signature args - bool isShortCircuit = false; - QStringList argsSignature = PySide::Signal::getArgsFromSignature(it->d->signature, - &isShortCircuit); - qsizetype signatureArgs = argsSignature.size(); - // Iterate the possible types of connection for this signal and compare // it with slot arguments - if (signatureArgs != slotArgs) { - while (it->d->next != nullptr) { - it = it->d->next; - argsSignature = PySide::Signal::getArgsFromSignature(it->d->signature, - &isShortCircuit); - signatureArgs = argsSignature.size(); - if (signatureArgs == slotArgs) { - matchedSlot = true; - break; - } - } + for (int slotArgs = slotArgRange.max; + slotArgs >= slotArgRange.min && matchedSlot == nullptr; --slotArgs) { + matchedSlot = findSignalInstance(source, slotArgs); } } // Adding references to pyArgs PyList_Append(pyArgs, source->d->source); - if (matchedSlot) { + if (matchedSlot != nullptr) { // If a slot matching the same number of arguments was found, // include signature to the pyArgs - Shiboken::AutoDecRef signature(PySide::Signal::buildQtCompatible(it->d->signature)); + Shiboken::AutoDecRef signature(PySide::Signal::buildQtCompatible(matchedSlot->d->signature)); PyList_Append(pyArgs, signature); } else { // Try the first by default if the slot was not found @@ -552,10 +560,8 @@ static PyObject *signalInstanceConnect(PyObject *self, PyObject *args, PyObject Shiboken::AutoDecRef tupleArgs(PyList_AsTuple(pyArgs)); Shiboken::AutoDecRef pyMethod(PyObject_GetAttr(source->d->source, PySide::PySideName::qtConnect())); - if (pyMethod.isNull()) { // PYSIDE-79: check if pyMethod exists. - PyErr_SetString(PyExc_RuntimeError, "method 'connect' vanished!"); - return nullptr; - } + if (pyMethod.isNull()) // PYSIDE-79: check if pyMethod exists. + return PyErr_Format(PyExc_RuntimeError, "method 'connect' vanished!"); PyObject *result = PyObject_CallObject(pyMethod, tupleArgs); if (connection_Check(result)) return result; @@ -575,17 +581,13 @@ static int argCountInSignature(const char *signature) static PyObject *signalInstanceEmit(PyObject *self, PyObject *args) { PySideSignalInstance *source = reinterpret_cast(self); - if (!source->d) { - PyErr_Format(PyExc_RuntimeError, "cannot emit uninitialized SignalInstance"); - return nullptr; - } + if (!source->d) + return PyErr_Format(PyExc_RuntimeError, "cannot emit uninitialized SignalInstance"); // PYSIDE-2201: Check if the object has vanished meanwhile. // Tried to revive it without exception, but this gives problems. - if (source->deleted) { - PyErr_Format(PyExc_RuntimeError, "The SignalInstance object was already deleted"); - return nullptr; - } + if (source->deleted) + return PyErr_Format(PyExc_RuntimeError, "The SignalInstance object was already deleted"); Shiboken::AutoDecRef pyArgs(PyList_New(0)); int numArgsGiven = PySequence_Fast_GET_SIZE(args); @@ -645,17 +647,26 @@ static PyObject *signalInstanceGetItem(PyObject *self, PyObject *key) message += '"' + data->d->signature + '"'; } - PyErr_SetString(PyExc_IndexError, message.constData()); - return nullptr; + return PyErr_Format(PyExc_IndexError, message.constData()); +} + +static inline void warnDisconnectFailed(PyObject *aSlot, const QByteArray &signature) +{ + if (PyErr_Occurred() != nullptr) { // avoid "%S" invoking str() when an error is set. + PyErr_WarnFormat(PyExc_RuntimeWarning, 0, "Failed to disconnect (%s) from signal \"%s\".", + Py_TYPE(aSlot)->tp_name, signature.constData()); + } else { + PyErr_WarnFormat(PyExc_RuntimeWarning, 0, "Failed to disconnect (%S) from signal \"%s\".", + aSlot, signature.constData()); + } } static PyObject *signalInstanceDisconnect(PyObject *self, PyObject *args) { auto source = reinterpret_cast(self); - if (!source->d) { - PyErr_Format(PyExc_RuntimeError, "cannot disconnect uninitialized SignalInstance"); - return nullptr; - } + if (!source->d) + return PyErr_Format(PyExc_RuntimeError, "cannot disconnect uninitialized SignalInstance"); + Shiboken::AutoDecRef pyArgs(PyList_New(0)); PyObject *slot = Py_None; @@ -696,14 +707,13 @@ static PyObject *signalInstanceDisconnect(PyObject *self, PyObject *args) Shiboken::AutoDecRef pyMethod(PyObject_GetAttr(source->d->source, PySide::PySideName::qtDisconnect())); PyObject *result = PyObject_CallObject(pyMethod, tupleArgs); - if (!result || result == Py_True) - return result; - Py_DECREF(result); + if (result != Py_True) + warnDisconnectFailed(slot, source->d->signature); + return result; } - PyErr_Format(PyExc_RuntimeError, "Failed to disconnect (%S) from signal \"%s\".", - slot, source->d->signature.constData()); - return nullptr; + warnDisconnectFailed(slot, source->d->signature); + Py_RETURN_FALSE; } // PYSIDE-68: Supply the missing __get__ function @@ -738,23 +748,19 @@ static PyObject *signalCall(PyObject *self, PyObject *args, PyObject *kw) // The only way calling a signal can succeed (the Python equivalent of C++'s operator() ) // is when a method with the same name as the signal is attached to an object. // An example is QProcess::error() (don't check the docs, but the source code of qprocess.h). - if (!signal->homonymousMethod) { - PyErr_SetString(PyExc_TypeError, "native Qt signal is not callable"); - return nullptr; - } - - descrgetfunc getDescriptor = Py_TYPE(signal->homonymousMethod)->tp_descr_get; + if (!signal->homonymousMethod) + return PyErr_Format(PyExc_TypeError, "native Qt signal is not callable"); // Check if there exists a method with the same name as the signal, which is also a static // method in C++ land. - Shiboken::AutoDecRef homonymousMethod(getDescriptor(signal->homonymousMethod, - nullptr, nullptr)); + Shiboken::AutoDecRef homonymousMethod(PepExt_Type_CallDescrGet(signal->homonymousMethod, + nullptr, nullptr)); if (PyCFunction_Check(homonymousMethod.object()) && (PyCFunction_GET_FLAGS(homonymousMethod.object()) & METH_STATIC)) return PyObject_Call(homonymousMethod, args, kw); // Assumes homonymousMethod is not a static method. - ternaryfunc callFunc = Py_TYPE(signal->homonymousMethod)->tp_call; + ternaryfunc callFunc = PepExt_Type_GetCallSlot(Py_TYPE(signal->homonymousMethod)); return callFunc(homonymousMethod, args, kw); } @@ -811,8 +817,8 @@ static PyObject *signalInstanceCall(PyObject *self, PyObject *args, PyObject *kw return nullptr; } - descrgetfunc getDescriptor = Py_TYPE(hom)->tp_descr_get; - Shiboken::AutoDecRef homonymousMethod(getDescriptor(hom, PySideSignal->d->source, nullptr)); + Shiboken::AutoDecRef homonymousMethod(PepExt_Type_CallDescrGet(hom, PySideSignal->d->source, + nullptr)); return PyObject_Call(homonymousMethod, args, kw); } @@ -843,7 +849,7 @@ static const char *SignalInstance_SignatureStrings[] = { "PySide6.QtCore.SignalInstance.connect(self,slot:object," "type:PySide6.QtCore.Qt.ConnectionType=PySide6.QtCore.Qt.ConnectionType.AutoConnection)" "->PySide6.QtCore.QMetaObject.Connection", - "PySide6.QtCore.SignalInstance.disconnect(self,slot:object=nullptr)", + "PySide6.QtCore.SignalInstance.disconnect(self,slot:object=nullptr)->bool", "PySide6.QtCore.SignalInstance.emit(self,*args:typing.Any)", nullptr}; // Sentinel @@ -964,9 +970,10 @@ static QByteArray buildSignature(const QByteArray &name, const QByteArray &signa static PySideSignalData::Signature parseSignature(PyObject *args) { - PySideSignalData::Signature result{{}, QMetaMethod::Compatibility}; + PySideSignalData::Signature result{{}, QMetaMethod::Compatibility, 0}; if (args && (Shiboken::String::check(args) || !PyTuple_Check(args))) { result.signature = getTypeName(args); + result.argCount = 1; return result; } @@ -977,6 +984,7 @@ static PySideSignalData::Signature parseSignature(PyObject *args) if (!result.signature.isEmpty()) result.signature += ','; result.signature += typeName; + ++result.argCount; } } return result; @@ -1001,6 +1009,7 @@ static void instanceInitialize(PySideSignalInstance *self, PyObject *name, PySid selfPvt->source = source; const auto &signature = signal->data->signatures.at(index); selfPvt->signature = buildSignature(self->d->signalName, signature.signature); + selfPvt->argCount = signature.argCount; selfPvt->attributes = signature.attributes; selfPvt->homonymousMethod = nullptr; if (signal->homonymousMethod) { @@ -1078,6 +1087,7 @@ PySideSignalInstance *newObjectFromMethod(PyObject *source, const QListsignalName = cppName; selfPvt->signature = m.methodSignature(); + selfPvt->argCount = int(m.parameterCount()); selfPvt->attributes = m.attributes(); selfPvt->homonymousMethod = nullptr; selfPvt->next = nullptr; @@ -1113,35 +1123,45 @@ static PyObject *buildQtCompatible(const QByteArray &signature) void registerSignals(PyTypeObject *pyObj, const QMetaObject *metaObject) { using Signature = PySideSignalData::Signature; - using SignalSigMap = QHash>; - SignalSigMap signalsFound; + struct MetaSignal + { + QByteArray methodName; + QList signatures; + }; + + QList signalsFound; for (int i = metaObject->methodOffset(), max = metaObject->methodCount(); i < max; ++i) { QMetaMethod method = metaObject->method(i); if (method.methodType() == QMetaMethod::Signal) { QByteArray methodName(method.methodSignature()); - methodName.chop(methodName.size() - methodName.indexOf('(')); - Signature signature{method.parameterTypes().join(','), {}}; + methodName.truncate(methodName.indexOf('(')); + Signature signature{method.parameterTypes().join(','), {}, + short(method.parameterCount())}; if (method.attributes() & QMetaMethod::Cloned) signature.attributes = QMetaMethod::Cloned; - signalsFound[methodName] << signature; + auto it = std::find_if(signalsFound.begin(), signalsFound.end(), + [methodName](const MetaSignal &ms) + { return ms.methodName == methodName; }); + if (it != signalsFound.end()) + it->signatures << signature; + else + signalsFound.append(MetaSignal{methodName, {signature}}); } } - SignalSigMap::Iterator it = signalsFound.begin(); - SignalSigMap::Iterator end = signalsFound.end(); - for (; it != end; ++it) { + for (const auto &metaSignal : std::as_const(signalsFound)) { PySideSignal *self = PyObject_New(PySideSignal, PySideSignal_TypeF()); self->data = new PySideSignalData; - self->data->signalName = it.key(); + self->data->signalName = metaSignal.methodName; self->homonymousMethod = nullptr; // Empty signatures comes first! So they will be the default signal signature - self->data->signatures = it.value(); + self->data->signatures = metaSignal.signatures; std::stable_sort(self->data->signatures.begin(), self->data->signatures.end(), &compareSignals); - _addSignalToWrapper(pyObj, it.key(), self); + _addSignalToWrapper(pyObj, metaSignal.methodName, self); Py_DECREF(reinterpret_cast(self)); } } @@ -1167,29 +1187,30 @@ EmitterData getEmitterData(PySideSignalInstance *signal) return result; } -QStringList getArgsFromSignature(const char *signature, bool *isShortCircuit) +QByteArrayList getArgsFromSignature(const char *signature, bool *isShortCircuit) { - QString qsignature = QString::fromLatin1(signature).trimmed(); - QStringList result; + QByteArray qsignature = QByteArray(signature).trimmed(); + QByteArrayList result; if (isShortCircuit) *isShortCircuit = !qsignature.contains(u'('); - if (qsignature.contains(u"()") || qsignature.contains(u"(void)")) + if (qsignature.contains("()") || qsignature.contains("(void)")) return result; - if (qsignature.endsWith(u')')) { - const int paren = qsignature.indexOf(u'('); + if (qsignature.endsWith(')')) { + const auto paren = qsignature.indexOf('('); if (paren >= 0) { qsignature.chop(1); qsignature.remove(0, paren + 1); result = qsignature.split(u','); - for (QString &type : result) + for (auto &type : result) type = type.trimmed(); } } return result; } -QString getCallbackSignature(const char *signal, QObject *receiver, PyObject *callback, bool encodeName) +QByteArray getCallbackSignature(const char *signal, QObject *receiver, + PyObject *callback, bool encodeName) { QByteArray functionName; qsizetype numArgs = -1; @@ -1198,7 +1219,7 @@ QString getCallbackSignature(const char *signal, QObject *receiver, PyObject *ca qsizetype useSelf = slotArgs.isMethod ? 1 : 0; if (slotArgs.function != nullptr) { - numArgs = argCount(slotArgs); + numArgs = argCount(slotArgs).max; #ifdef PYPY_VERSION } else if (Py_TYPE(callback) == PepBuiltinMethod_TypePtr) { // PYSIDE-535: PyPy has a special builtin method that acts almost like PyCFunction. @@ -1262,9 +1283,8 @@ QString getCallbackSignature(const char *signal, QObject *receiver, PyObject *ca functionName[0] = '_'; functionName[functionName.size() - 1] = '_'; } - const QString functionNameS = QLatin1String(functionName); - QString signature = encodeName ? codeCallbackName(callback, functionNameS) : functionNameS; - QStringList args = getArgsFromSignature(signal, &isShortCircuit); + QByteArray signature = encodeName ? codeCallbackName(callback, functionName) : functionName; + QByteArrayList args = getArgsFromSignature(signal, &isShortCircuit); if (!isShortCircuit) { signature.append(u'('); @@ -1272,8 +1292,8 @@ QString getCallbackSignature(const char *signal, QObject *receiver, PyObject *ca numArgs = std::numeric_limits::max(); while (!args.isEmpty() && (args.size() > (numArgs - useSelf))) args.removeLast(); - signature.append(args.join(u',')); - signature.append(u')'); + signature.append(args.join(',')); + signature.append(')'); } return signature; } @@ -1292,21 +1312,21 @@ bool checkQtSignal(const char *signal) return true; } -QString codeCallbackName(PyObject *callback, const QString &funcName) +QByteArray codeCallbackName(PyObject *callback, const QByteArray &funcName) { if (PyMethod_Check(callback)) { PyObject *self = PyMethod_GET_SELF(callback); PyObject *func = PyMethod_GET_FUNCTION(callback); - return funcName + QString::number(quint64(self), 16) + QString::number(quint64(func), 16); + return funcName + QByteArray::number(quint64(self), 16) + QByteArray::number(quint64(func), 16); } // PYSIDE-1523: Handle the compiled case. if (PySide::isCompiledMethod(callback)) { // Not retaining references inline with what PyMethod_GET_(SELF|FUNC) does. Shiboken::AutoDecRef self(PyObject_GetAttr(callback, PySide::PySideName::im_self())); Shiboken::AutoDecRef func(PyObject_GetAttr(callback, PySide::PySideName::im_func())); - return funcName + QString::number(quint64(self), 16) + QString::number(quint64(func), 16); + return funcName + QByteArray::number(quint64(self), 16) + QByteArray::number(quint64(func), 16); } - return funcName + QString::number(quint64(callback), 16); + return funcName + QByteArray::number(quint64(callback), 16); } QByteArray voidType() diff --git a/sources/pyside6/libpyside/pysidesignal.h b/sources/pyside6/libpyside/pysidesignal.h index 821a49fa..589fc2c9 100644 --- a/sources/pyside6/libpyside/pysidesignal.h +++ b/sources/pyside6/libpyside/pysidesignal.h @@ -135,7 +135,8 @@ PYSIDE_API bool checkQtSignal(const char *signature); * @param encodeName Used to specify if the returned signature will be encoded with Qt signal/slot style * @return Return the callback signature **/ -PYSIDE_API QString getCallbackSignature(const char *signal, QObject *receiver, PyObject *callback, bool encodeName); +PYSIDE_API QByteArray getCallbackSignature(const char *signal, QObject *receiver, + PyObject *callback, bool encodeName); /** * This function parses the signature and then returns a list of argument types. @@ -145,8 +146,8 @@ PYSIDE_API QString getCallbackSignature(const char *signal, QObject *receiver, P * @return Return true if this is a Qt Signal, otherwise return false * @todo replace return type by QList **/ -QStringList getArgsFromSignature(const char *signature, - bool *isShortCircuit = nullptr); +QByteArrayList getArgsFromSignature(const char *signature, + bool *isShortCircuit = nullptr); } // namespace PySide::Signal diff --git a/sources/pyside6/libpyside/pysidesignal_p.h b/sources/pyside6/libpyside/pysidesignal_p.h index 8a0feea4..55a9a7a7 100644 --- a/sources/pyside6/libpyside/pysidesignal_p.h +++ b/sources/pyside6/libpyside/pysidesignal_p.h @@ -15,6 +15,7 @@ struct PySideSignalData { QByteArray signature; // ','-separated list of parameter types unsigned short attributes; + short argCount; }; QByteArray signalName; @@ -43,6 +44,7 @@ struct PySideSignalInstancePrivate PyObject *homonymousMethod = nullptr; PySideSignalInstance *next = nullptr; unsigned short attributes = 0; + short argCount = 0; }; namespace PySide::Signal { @@ -50,7 +52,7 @@ namespace PySide::Signal { void init(PyObject *module); bool connect(PyObject *source, const char *signal, PyObject *callback); QByteArray getTypeName(PyObject *); - QString codeCallbackName(PyObject *callback, const QString &funcName); + QByteArray codeCallbackName(PyObject *callback, const QByteArray &funcName); QByteArray voidType(); } // namespace PySide::Signal diff --git a/sources/pyside6/libpyside/pysidestaticstrings.cpp b/sources/pyside6/libpyside/pysidestaticstrings.cpp index 9105fff6..3bddc84c 100644 --- a/sources/pyside6/libpyside/pysidestaticstrings.cpp +++ b/sources/pyside6/libpyside/pysidestaticstrings.cpp @@ -23,6 +23,7 @@ STATIC_STRING_IMPL(fset, "fset") STATIC_STRING_IMPL(im_func, "im_func") STATIC_STRING_IMPL(im_self, "im_self") STATIC_STRING_IMPL(name, "name") +STATIC_STRING_IMPL(orig_dict, "orig_dict") STATIC_STRING_IMPL(parameters, "parameters") STATIC_STRING_IMPL(property, "property") STATIC_STRING_IMPL(select_id, "select_id") diff --git a/sources/pyside6/libpyside/pysidestaticstrings.h b/sources/pyside6/libpyside/pysidestaticstrings.h index 3d00fac6..b4bc6180 100644 --- a/sources/pyside6/libpyside/pysidestaticstrings.h +++ b/sources/pyside6/libpyside/pysidestaticstrings.h @@ -19,6 +19,7 @@ PYSIDE_API PyObject *fset(); PYSIDE_API PyObject *im_func(); PYSIDE_API PyObject *im_self(); PYSIDE_API PyObject *name(); +PYSIDE_API PyObject *orig_dict(); PYSIDE_API PyObject *parameters(); PYSIDE_API PyObject *property(); PYSIDE_API PyObject *select_id(); diff --git a/sources/pyside6/libpyside/pysideutils.h b/sources/pyside6/libpyside/pysideutils.h index d3590510..47c2f2c1 100644 --- a/sources/pyside6/libpyside/pysideutils.h +++ b/sources/pyside6/libpyside/pysideutils.h @@ -8,7 +8,7 @@ #include -#include +#include QT_FORWARD_DECLARE_CLASS(QDebug) QT_FORWARD_DECLARE_CLASS(QString) @@ -55,6 +55,15 @@ struct debugPyObject PYSIDE_API QDebug operator<<(QDebug debug, const debugPyObject &o); +struct debugPyBuffer +{ + PYSIDE_API explicit debugPyBuffer(Py_buffer *b) noexcept; + + Py_buffer *m_buffer; +}; + +PYSIDE_API QDebug operator<<(QDebug debug, const debugPyBuffer &b); + } //namespace PySide #endif // PYSIDESTRING_H diff --git a/sources/pyside6/libpyside/qobjectconnect.cpp b/sources/pyside6/libpyside/qobjectconnect.cpp index c47c96eb..3c5b7595 100644 --- a/sources/pyside6/libpyside/qobjectconnect.cpp +++ b/sources/pyside6/libpyside/qobjectconnect.cpp @@ -132,7 +132,7 @@ static GetReceiverResult getReceiver(QObject *source, const char *signal, if (!result.usingGlobalReceiver && result.receiver && result.self) { result.callbackSig = PySide::Signal::getCallbackSignature(signal, result.receiver, callback, - result.usingGlobalReceiver).toLatin1(); + result.usingGlobalReceiver); const QMetaObject *metaObject = result.receiver->metaObject(); result.slotIndex = metaObject->indexOfSlot(result.callbackSig.constData()); if (PyMethod_Check(callback) != 0 && result.slotIndex != -1 @@ -158,7 +158,7 @@ static GetReceiverResult getReceiver(QObject *source, const char *signal, result.receiver->moveToThread(receiverThread); result.callbackSig = PySide::Signal::getCallbackSignature(signal, result.receiver, callback, - result.usingGlobalReceiver).toLatin1(); + result.usingGlobalReceiver); const QMetaObject *metaObject = result.receiver->metaObject(); result.slotIndex = metaObject->indexOfSlot(result.callbackSig.constData()); } diff --git a/sources/pyside6/libpyside/signalmanager.cpp b/sources/pyside6/libpyside/signalmanager.cpp index b4e1bb48..557f130e 100644 --- a/sources/pyside6/libpyside/signalmanager.cpp +++ b/sources/pyside6/libpyside/signalmanager.cpp @@ -21,15 +21,19 @@ #include #include +#include #include #include #include #include +#include #include #include #include +using namespace Qt::StringLiterals; + #if QSLOT_CODE != 1 || QSIGNAL_CODE != 2 #error QSLOT_CODE and/or QSIGNAL_CODE changed! change the hardcoded stuff to the correct value! #endif @@ -38,8 +42,11 @@ #include "globalreceiverv2.h" static PyObject *metaObjectAttr = nullptr; -static PyObject *parseArguments(const QList< QByteArray >& paramTypes, void **args); +static PyObject *parseArguments(const QMetaMethod &method, void **args); static bool emitShortCircuitSignal(QObject *source, int signalIndex, PyObject *args); + +static bool qAppRunning = false; + static void destroyMetaObject(PyObject *obj) { void *ptr = PyCapsule_GetPointer(obj, nullptr); @@ -68,6 +75,29 @@ static const char *metaCallName(QMetaObject::Call call) return it != mapping.constEnd() ? it.value() : ""; } +static QByteArray methodSignature(const QMetaMethod &method) +{ + QByteArray result; + if (auto *t = method.typeName()) { + result += t; + result += ' '; + } + result += method.methodSignature(); + return result; +} + +static QByteArray msgCannotConvertParameter(const QMetaMethod &method, qsizetype p) +{ + return "Cannot call meta function \""_ba + methodSignature(method) + + "\" because parameter " + QByteArray::number(p) + " of type \""_ba + + method.parameterTypeName(p) + "\" cannot be converted."_ba; +} + +static QByteArray msgCannotConvertReturn(const QMetaMethod &method) +{ + return "The return value of \""_ba + methodSignature(method) + "\" cannot be converted."_ba; +} + namespace PySide { PyObjectWrapper::PyObjectWrapper() @@ -199,6 +229,39 @@ using GlobalReceiverV2Map = QHashtimerId() == m_timerId) { + killTimer(std::exchange(m_timerId, -1)); + SignalManager::instance().purgeEmptyGlobalReceivers(); + } +} + struct SignalManager::SignalManagerPrivate { Q_DISABLE_COPY_MOVE(SignalManagerPrivate) @@ -206,9 +269,9 @@ struct SignalManager::SignalManagerPrivate SignalManagerPrivate() noexcept = default; ~SignalManagerPrivate() { clear(); } - void deleteGobalReceiver(const QObject *gr); + void deleteGlobalReceiver(const QObject *gr); void clear(); - void purgeEmptyGobalReceivers(); + void purgeEmptyGlobalReceivers(); GlobalReceiverV2Map m_globalReceivers; static SignalManager::QmlMetaCallErrorHandler m_qmlMetaCallErrorHandler; @@ -217,6 +280,8 @@ struct SignalManager::SignalManagerPrivate static int qtPropertyMetacall(QObject *object, QMetaObject::Call call, int id, void **args); static int qtMethodMetacall(QObject *object, int id, void **args); + + QPointer m_listener; }; SignalManager::QmlMetaCallErrorHandler @@ -288,8 +353,32 @@ void SignalManager::setQmlMetaCallErrorHandler(QmlMetaCallErrorHandler handler) SignalManagerPrivate::m_qmlMetaCallErrorHandler = handler; } +static void qAppAboutToQuit() +{ + qAppRunning = false; + SignalManager::instance().purgeEmptyGlobalReceivers(); +} + +static bool isInMainThread(const QObject *o) +{ + if (o->isWidgetType() || o->isWindowType() || o->isQuickItemType()) + return true; + auto *app = QCoreApplication::instance(); + return app != nullptr && app->thread() == o->thread(); +} + QObject *SignalManager::globalReceiver(QObject *sender, PyObject *callback, QObject *receiver) { + if (m_d->m_listener.isNull() && !QCoreApplication::closingDown()) { + if (auto *app = QCoreApplication::instance()) { + // The signal manager potentially outlives QCoreApplication, ensure deletion + m_d->m_listener = new SignalManagerDestroyListener(app); + m_d->m_listener->setObjectName("qt_pyside_signalmanagerdestroylistener"); + QObject::connect(app, &QCoreApplication::aboutToQuit, qAppAboutToQuit); + qAppRunning = true; + } + } + auto &globalReceivers = m_d->m_globalReceivers; const GlobalReceiverKey key = GlobalReceiverV2::key(callback); auto it = globalReceivers.find(key); @@ -297,16 +386,30 @@ QObject *SignalManager::globalReceiver(QObject *sender, PyObject *callback, QObj auto gr = std::make_shared(callback, receiver); it = globalReceivers.insert(key, gr); } - if (sender) + + if (sender != nullptr) { it.value()->incRef(sender); // create a link reference + // For main thread-objects, add a notification for destroy (PYSIDE-2646, 2141) + if (qAppRunning && !m_d->m_listener.isNull() && isInMainThread(sender)) { + QObject::connect(sender, &QObject::destroyed, + m_d->m_listener, &SignalManagerDestroyListener::destroyNotify, + Qt::UniqueConnection); + } + } + return it.value().get(); } +void SignalManager::purgeEmptyGlobalReceivers() +{ + m_d->purgeEmptyGlobalReceivers(); +} + void SignalManager::notifyGlobalReceiver(QObject *receiver) { reinterpret_cast(receiver)->notify(); - m_d->purgeEmptyGobalReceivers(); + purgeEmptyGlobalReceivers(); } void SignalManager::releaseGlobalReceiver(const QObject *source, QObject *receiver) @@ -314,15 +417,15 @@ void SignalManager::releaseGlobalReceiver(const QObject *source, QObject *receiv auto gr = static_cast(receiver); gr->decRef(source); if (gr->isEmpty()) - m_d->deleteGobalReceiver(gr); + m_d->deleteGlobalReceiver(gr); } -void SignalManager::deleteGobalReceiver(const QObject *gr) +void SignalManager::deleteGlobalReceiver(const QObject *gr) { - SignalManager::instance().m_d->deleteGobalReceiver(gr); + SignalManager::instance().m_d->deleteGlobalReceiver(gr); } -void SignalManager::SignalManagerPrivate::deleteGobalReceiver(const QObject *gr) +void SignalManager::SignalManagerPrivate::deleteGlobalReceiver(const QObject *gr) { for (auto it = m_globalReceivers.begin(), end = m_globalReceivers.end(); it != end; ++it) { if (it.value().get() == gr) { @@ -347,7 +450,7 @@ static bool isEmptyGlobalReceiver(const GlobalReceiverV2Ptr &g) return g->isEmpty(); } -void SignalManager::SignalManagerPrivate::purgeEmptyGobalReceivers() +void SignalManager::SignalManagerPrivate::purgeEmptyGlobalReceivers() { // Delete repetitively (see comment in clear()). while (true) { @@ -537,34 +640,27 @@ int SignalManager::callPythonMetaMethod(const QMetaMethod &method, void **args, Q_ASSERT(pyMethod); Shiboken::GilState gil; - PyObject *pyArguments = nullptr; - - if (isShortCuit){ - pyArguments = reinterpret_cast(args[1]); - } else { - pyArguments = parseArguments(method.parameterTypes(), args); - } + PyObject *pyArguments = isShortCuit + ? reinterpret_cast(args[1]) : parseArguments(method, args); if (pyArguments) { QScopedPointer retConverter; const char *returnType = method.typeName(); - if (returnType && std::strcmp("", returnType) && std::strcmp("void", returnType)) { + if (returnType != nullptr && returnType[0] != 0 && std::strcmp("void", returnType) != 0) { retConverter.reset(new Shiboken::Conversions::SpecificConverter(returnType)); if (!retConverter->isValid()) { - PyErr_Format(PyExc_RuntimeError, "Can't find converter for '%s' to call Python meta method.", returnType); + PyErr_SetString(PyExc_RuntimeError, msgCannotConvertReturn(method).constData()); return -1; } } Shiboken::AutoDecRef retval(PyObject_CallObject(pyMethod, pyArguments)); - if (!isShortCuit && pyArguments){ + if (!isShortCuit && pyArguments) Py_DECREF(pyArguments); - } - if (!retval.isNull() && retval != Py_None && !PyErr_Occurred() && retConverter) { + if (!retval.isNull() && retval != Py_None && !PyErr_Occurred() && retConverter) retConverter->toCpp(retval, args[0]); - } } return -1; @@ -708,22 +804,22 @@ const QMetaObject *SignalManager::retrieveMetaObject(PyObject *self) return builder->update(); } -static PyObject *parseArguments(const QList& paramTypes, void **args) +static PyObject *parseArguments(const QMetaMethod &method, void **args) { + const auto ¶mTypes = method.parameterTypes(); const qsizetype argsSize = paramTypes.size(); PyObject *preparedArgs = PyTuple_New(argsSize); for (qsizetype i = 0; i < argsSize; ++i) { void *data = args[i+1]; - const char *dataType = paramTypes[i].constData(); - Shiboken::Conversions::SpecificConverter converter(dataType); - if (converter) { - PyTuple_SET_ITEM(preparedArgs, i, converter.toPython(data)); - } else { - PyErr_Format(PyExc_TypeError, "Can't call meta function because I have no idea how to handle %s", dataType); + auto param = paramTypes.at(i); + Shiboken::Conversions::SpecificConverter converter(param.constData()); + if (!converter) { + PyErr_SetString(PyExc_TypeError, msgCannotConvertParameter(method, i).constData()); Py_DECREF(preparedArgs); return nullptr; } + PyTuple_SET_ITEM(preparedArgs, i, converter.toPython(data)); } return preparedArgs; } @@ -734,3 +830,5 @@ static bool emitShortCircuitSignal(QObject *source, int signalIndex, PyObject *a source->qt_metacall(QMetaObject::InvokeMetaMethod, signalIndex, signalArgs); return true; } + +#include "signalmanager.moc" diff --git a/sources/pyside6/libpyside/signalmanager.h b/sources/pyside6/libpyside/signalmanager.h index df47c33b..c531d663 100644 --- a/sources/pyside6/libpyside/signalmanager.h +++ b/sources/pyside6/libpyside/signalmanager.h @@ -78,11 +78,12 @@ public: // Disconnect all signals managed by Globalreceiver void clear(); + void purgeEmptyGlobalReceivers(); // Utility function to call a python method usign args received in qt_metacall static int callPythonMetaMethod(const QMetaMethod& method, void** args, PyObject* obj, bool isShortCuit); - static void deleteGobalReceiver(const QObject *globalReceiver); + static void deleteGlobalReceiver(const QObject *globalReceiver); private: struct SignalManagerPrivate; diff --git a/sources/pyside6/libpysideqml/pysideqmlattached.cpp b/sources/pyside6/libpysideqml/pysideqmlattached.cpp index 930358d7..d484257e 100644 --- a/sources/pyside6/libpysideqml/pysideqmlattached.cpp +++ b/sources/pyside6/libpysideqml/pysideqmlattached.cpp @@ -14,7 +14,6 @@ #include #include -#include #include #include diff --git a/sources/pyside6/libpysideqml/pysideqmlattached.h b/sources/pyside6/libpysideqml/pysideqmlattached.h index 9365b68d..96f78826 100644 --- a/sources/pyside6/libpysideqml/pysideqmlattached.h +++ b/sources/pyside6/libpysideqml/pysideqmlattached.h @@ -8,7 +8,7 @@ #include "pysideqmlmacros.h" -#include +#include QT_FORWARD_DECLARE_CLASS(QObject) diff --git a/sources/pyside6/libpysideqml/pysideqmlextended.cpp b/sources/pyside6/libpysideqml/pysideqmlextended.cpp index 01479485..23543d58 100644 --- a/sources/pyside6/libpysideqml/pysideqmlextended.cpp +++ b/sources/pyside6/libpysideqml/pysideqmlextended.cpp @@ -12,7 +12,6 @@ #include #include -#include #include // The QmlExtended decorator modifies QmlElement to register an extension. diff --git a/sources/pyside6/libpysideqml/pysideqmlforeign.cpp b/sources/pyside6/libpysideqml/pysideqmlforeign.cpp index f9cc4f44..18d39d12 100644 --- a/sources/pyside6/libpysideqml/pysideqmlforeign.cpp +++ b/sources/pyside6/libpysideqml/pysideqmlforeign.cpp @@ -11,7 +11,6 @@ #include #include -#include #include // The QmlForeign decorator modifies QmlElement to create a different type diff --git a/sources/pyside6/libpysideqml/pysideqmllistproperty.cpp b/sources/pyside6/libpysideqml/pysideqmllistproperty.cpp index a6f9f019..75bb5af9 100644 --- a/sources/pyside6/libpysideqml/pysideqmllistproperty.cpp +++ b/sources/pyside6/libpysideqml/pysideqmllistproperty.cpp @@ -5,6 +5,7 @@ #include "pysideqmlregistertype_p.h" #include +#include #include #include @@ -33,7 +34,7 @@ extern "C" static PyObject *propList_tp_new(PyTypeObject *subtype, PyObject * /* args */, PyObject * /* kwds */) { - PySideProperty *me = reinterpret_cast(subtype->tp_alloc(subtype, 0)); + auto *me = PepExt_TypeCallAlloc(subtype, 0); me->d = new QmlListPropertyPrivate; return reinterpret_cast(me); } @@ -168,7 +169,7 @@ qsizetype propListCount(QQmlListProperty *propList) return 0; } - int cppResult = 0; + qsizetype cppResult = 0; auto *converter = Shiboken::Conversions::PrimitiveTypeConverter(); if (auto *pythonToCpp = Shiboken::Conversions::isPythonToCppConvertible(converter, retVal)) pythonToCpp(retVal, &cppResult); diff --git a/sources/pyside6/libpysideqml/pysideqmlmetacallerror.cpp b/sources/pyside6/libpysideqml/pysideqmlmetacallerror.cpp index 8e1381a4..63cefedb 100644 --- a/sources/pyside6/libpysideqml/pysideqmlmetacallerror.cpp +++ b/sources/pyside6/libpysideqml/pysideqmlmetacallerror.cpp @@ -45,7 +45,7 @@ std::optional qmlMetaCallErrorHandler(QObject *object) // PYSIDE-464: The error is only valid before PyErr_Restore, // PYSIDE-464: therefore we take local copies. Shiboken::AutoDecRef objStr(PyObject_Str(errValue)); - const QString errString = QLatin1String(Shiboken::String::toCString(objStr)); + const QString errString = QString::fromUtf8(Shiboken::String::toCString(objStr)); const bool isSyntaxError = errType == PyExc_SyntaxError; const bool isTypeError = errType == PyExc_TypeError; PyErr_Restore(errType, errValue, errTraceback); diff --git a/sources/pyside6/libpysideqml/pysideqmlmetacallerror_p.h b/sources/pyside6/libpysideqml/pysideqmlmetacallerror_p.h index 745ade51..fcbb6395 100644 --- a/sources/pyside6/libpysideqml/pysideqmlmetacallerror_p.h +++ b/sources/pyside6/libpysideqml/pysideqmlmetacallerror_p.h @@ -6,7 +6,7 @@ #include -#include +#include QT_FORWARD_DECLARE_CLASS(QObject) diff --git a/sources/pyside6/libpysideqml/pysideqmlnamedelement.cpp b/sources/pyside6/libpysideqml/pysideqmlnamedelement.cpp index f2963801..faf3e411 100644 --- a/sources/pyside6/libpysideqml/pysideqmlnamedelement.cpp +++ b/sources/pyside6/libpysideqml/pysideqmlnamedelement.cpp @@ -2,15 +2,12 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "pysideqmlnamedelement_p.h" -#include "pysideqmltypeinfo_p.h" #include #include #include #include -#include - class PySideQmlNamedElementPrivate : public PySide::ClassDecorator::StringDecoratorPrivate { public: @@ -31,7 +28,7 @@ PyObject *PySideQmlNamedElementPrivate::tp_call(PyObject *self, PyObject *args, return nullptr; auto *data = DecoratorPrivate::get(self); - auto *result = PySide::Qml::qmlNamedElementMacro(klass, data->string().c_str()); + auto *result = PySide::Qml::qmlNamedElementMacro(klass, data->string()); Py_XINCREF(result); return result; } diff --git a/sources/pyside6/libpysideqml/pysideqmlregistertype.cpp b/sources/pyside6/libpysideqml/pysideqmlregistertype.cpp index 1208220c..4ccd459d 100644 --- a/sources/pyside6/libpysideqml/pysideqmlregistertype.cpp +++ b/sources/pyside6/libpysideqml/pysideqmlregistertype.cpp @@ -6,8 +6,10 @@ #include "pysideqmltypeinfo_p.h" #include "pysideqmlattached_p.h" #include "pysideqmlextended_p.h" +#include "pysideqmluncreatable.h" #include +#include // shiboken #include @@ -16,6 +18,7 @@ // pyside #include #include +#include #include #include @@ -25,9 +28,16 @@ #include #include #include +#include + +#include + +using namespace Qt::StringLiterals; static PySide::Qml::QuickRegisterItemFunction quickRegisterItemFunction = nullptr; +static const auto qmlElementKey = "QML.Element"_ba; + static void createInto(void *memory, void *type) { QMutexLocker locker(&PySide::nextQObjectMemoryAddrMutex()); @@ -85,25 +95,112 @@ static inline bool isQmlParserStatus(const QMetaObject *o) return inheritsFrom(o, "QPyQmlParserStatus"); } -namespace PySide::Qml { +static QByteArray getGlobalString(const char *name) +{ + PyObject *globalVar = PyDict_GetItemString(PyEval_GetGlobals(), name); -int qmlRegisterType(PyObject *pyObj, const char *uri, int versionMajor, - int versionMinor, const char *qmlName, const char *noCreationReason, - bool creatable) + if (globalVar == nullptr || PyUnicode_Check(globalVar) == 0) + return {}; + + const char *stringValue = _PepUnicode_AsString(globalVar); + return stringValue != nullptr ? QByteArray(stringValue) : QByteArray{}; +} + +static int getGlobalInt(const char *name) { - using namespace Shiboken; + PyObject *globalVar = PyDict_GetItemString(PyEval_GetGlobals(), name); - PyTypeObject *qobjectType = qObjectType(); + if (globalVar == nullptr || PyLong_Check(globalVar) == 0) + return -1; - PyTypeObject *pyObjType = reinterpret_cast(pyObj); - if (!PySequence_Contains(pyObjType->tp_mro, reinterpret_cast(qobjectType))) { - PyErr_Format(PyExc_TypeError, "A type inherited from %s expected, got %s.", - qobjectType->tp_name, pyObjType->tp_name); + long value = PyLong_AsLong(globalVar); + + if (value > std::numeric_limits::max() || value < std::numeric_limits::min()) return -1; + + return value; +} + +struct ImportData +{ + QByteArray importName; + int majorVersion = 0; + int minorVersion = 0; + + QTypeRevision toTypeRevision() const; +}; + +QTypeRevision ImportData::toTypeRevision() const +{ + return QTypeRevision::fromVersion(majorVersion, minorVersion); +} + +std::optional getGlobalImportData(const char *decoratorName) +{ + ImportData result{getGlobalString("QML_IMPORT_NAME"), + getGlobalInt("QML_IMPORT_MAJOR_VERSION"), + getGlobalInt("QML_IMPORT_MINOR_VERSION")}; + + if (result.importName.isEmpty()) { + PyErr_Format(PyExc_TypeError, "You need specify QML_IMPORT_NAME in order to use %s.", + decoratorName); + return {}; } - const QMetaObject *metaObject = PySide::retrieveMetaObject(pyObjType); - Q_ASSERT(metaObject); + if (result.majorVersion == -1) { + PyErr_Format(PyExc_TypeError, "You need specify QML_IMPORT_MAJOR_VERSION in order to use %s.", + decoratorName); + return {}; + } + + // Specifying a minor version is optional + if (result.minorVersion == -1) + result.minorVersion = 0; + return result; +} + +static PyTypeObject *checkTypeObject(PyObject *pyObj, const char *what) +{ + if (PyType_Check(pyObj) == 0) { + PyErr_Format(PyExc_TypeError, "%s can only be used for classes.", what); + return nullptr; + } + return reinterpret_cast(pyObj); +} + +static bool setClassInfo(PyTypeObject *type, const QByteArray &key, const QByteArray &value) +{ + if (!PySide::ClassInfo::setClassInfo(type, key, value)) { + PyErr_Format(PyExc_TypeError, "Setting class info \"%s\" to \"%s\" on \"%s\" failed.", + key.constData(), value.constData(), type->tp_name); + return false; + } + return true; +} + +static inline bool setSingletonClassInfo(PyTypeObject *type) +{ + return setClassInfo(type, "QML.Singleton"_ba, "true"_ba); +} + +static QQmlCustomParser *defaultCustomParserFactory() +{ + return nullptr; +} + +namespace PySide::Qml { + +// Modern (6.7) type registration using RegisterTypeAndRevisions +// and information set to QMetaClassInfo. +static int qmlRegisterType(PyObject *pyObj, + const ImportData &importData, + const QMetaObject *metaObject, + const QMetaObject *classInfoMetaObject = nullptr) +{ + PyTypeObject *pyObjType = reinterpret_cast(pyObj); + + if (classInfoMetaObject == nullptr) + classInfoMetaObject = metaObject; // Register as simple QObject rather than Qt Quick item. // Incref the type object, don't worry about decref'ing it because @@ -121,26 +218,27 @@ int qmlRegisterType(PyObject *pyObj, const char *uri, int versionMajor, const auto attachedInfo = qmlAttachedInfo(pyObjType, typeInfo); const auto extendedInfo = qmlExtendedInfo(pyObj, typeInfo); - QQmlPrivate::RegisterType type { + QList ids; + QQmlPrivate::RegisterTypeAndRevisions type { QQmlPrivate::RegisterType::StructVersion::Base, // structVersion typeId, listId, objectSize, - creatable ? createInto : nullptr, // create + createInto, // create pyObj, // userdata - QString::fromUtf8(noCreationReason), nullptr, // createValueType (Remove in Qt 7) - uri, - QTypeRevision::fromVersion(versionMajor, versionMinor), // version - qmlName, // elementName + importData.importName.constData(), + importData.toTypeRevision(), // version metaObject, + classInfoMetaObject, attachedInfo.factory, // attachedPropertiesFunction attachedInfo.metaObject, // attachedPropertiesMetaObject 0, 0, 0, // parserStatusCast, valueSourceCast, valueInterceptorCast extendedInfo.factory, // extensionObjectCreate extendedInfo.metaObject, // extensionMetaObject - nullptr, // customParser - {}, // revision + defaultCustomParserFactory, // customParser + &ids, // qmlTypeIds 0, // finalizerCast - QQmlPrivate::ValueTypeCreationMethod::None // creationMethod + false, // forceAnonymous + {} // listMetaSequence }; // Allow registering Qt Quick items. @@ -160,39 +258,245 @@ int qmlRegisterType(PyObject *pyObj, const char *uri, int versionMajor, QQmlPrivate::StaticCastSelector::cast(); } - int qmlTypeId = QQmlPrivate::qmlregister(QQmlPrivate::TypeRegistration, &type); + QQmlPrivate::qmlregister(QQmlPrivate::TypeAndRevisionsRegistration, &type); + const int qmlTypeId = ids.value(0, -1); if (qmlTypeId == -1) { PyErr_Format(PyExc_TypeError, "QML meta type registration of \"%s\" failed.", - qmlName); + typeName.constData()); } return qmlTypeId; } -int qmlRegisterSingletonType(PyObject *pyObj, const char *uri, int versionMajor, - int versionMinor, const char *qmlName, PyObject *callback, - bool isQObject, bool hasCallback) +static int qmlRegisterType(PyObject *pyObj, PyObject *pyClassInfoObj, + const ImportData &importData) { - using namespace Shiboken; + PyTypeObject *pyObjType = reinterpret_cast(pyObj); + if (!isQObjectDerived(pyObjType, true)) + return -1; - if (hasCallback) { - if (!PyCallable_Check(callback)) { - PyErr_Format(PyExc_TypeError, "Invalid callback specified."); - return -1; - } + const QMetaObject *metaObject = PySide::retrieveMetaObject(pyObjType); + Q_ASSERT(metaObject); + const QMetaObject *classInfoMetaObject = pyObj == pyClassInfoObj + ? metaObject : PySide::retrieveMetaObject(pyClassInfoObj); + return qmlRegisterType(pyObj, importData, metaObject, classInfoMetaObject); +} - AutoDecRef funcCode(PyObject_GetAttrString(callback, "__code__")); - AutoDecRef argCount(PyObject_GetAttrString(funcCode, "co_argcount")); +// Legacy (pre 6.7) compatibility helper for the free register functions. +int qmlRegisterType(PyObject *pyObj, const char *uri, int versionMajor, int versionMinor, + const char *qmlName, const char *noCreationReason, + bool creatable) +{ + auto *type = checkTypeObject(pyObj, "qmlRegisterType()"); + if (type == nullptr || !PySide::isQObjectDerived(type, true)) + return -1; - int count = PyLong_AsLong(argCount); + const QMetaObject *metaObject = PySide::retrieveMetaObject(type); + Q_ASSERT(metaObject); - if (count != 1) { - PyErr_Format(PyExc_TypeError, "Callback has a bad parameter count."); - return -1; + // PYSIDE-2709: Use a separate QMetaObject for the class information + // as modifying metaObject breaks inheritance. + QMetaObjectBuilder classInfobuilder(&QObject::staticMetaObject); + classInfobuilder.addClassInfo(qmlElementKey, qmlName); + if (!creatable) + setUncreatableClassInfo(&classInfobuilder, noCreationReason); + auto *classInfoMetaObject = classInfobuilder.toMetaObject(); + + const int qmlTypeId = qmlRegisterType(pyObj, {uri, versionMajor, versionMinor}, + metaObject, classInfoMetaObject); + free(classInfoMetaObject); + return qmlTypeId; +} + +// Singleton helpers + +// Check the arguments of a singleton callback (C++: "QJSValue cb(QQmlEngine *, QJSEngine *)", +// but we drop the QJSEngine since it will be the same as QQmlEngine when the latter exists. +static bool checkSingletonCallback(PyObject *callback) +{ + if (callback == nullptr) { + PyErr_SetString(PyExc_TypeError, "No callback specified."); + return false; + } + if (PyCallable_Check(callback) == 0) { + PyErr_Format(PyExc_TypeError, "Invalid callback specified (%S).", callback); + return false; + } + Shiboken::AutoDecRef funcCode(PyObject_GetAttrString(callback, "__code__")); + if (funcCode.isNull()) { + PyErr_Format(PyExc_TypeError, "Cannot retrieve code of callback (%S).", callback); + return false; + } + Shiboken::AutoDecRef argCountAttr(PyObject_GetAttrString(funcCode, "co_argcount")); + const int argCount = PyLong_AsLong(argCountAttr.object()); + if (argCount != 1) { + PyErr_Format(PyExc_TypeError, "Callback (%S) has %d parameter(s), expected one.", + callback, argCount); + return false; + } + + return true; +} + +// Shared data of a singleton creation callback which dereferences an object on +// destruction. +class SingletonQObjectCreationSharedData +{ +public: + Q_DISABLE_COPY_MOVE(SingletonQObjectCreationSharedData) + + SingletonQObjectCreationSharedData(PyObject *cb, PyObject *ref = nullptr) noexcept : + callable(cb), reference(ref) + { + Py_XINCREF(ref); + } + + // FIXME: Currently, the QML registration data are in global static variables + // and thus cleaned up after Python terminates. Once they are cleaned up + // by the QML engine, the code can be activated for proper cleanup of the references. + ~SingletonQObjectCreationSharedData() +#if 0 // + ~SingletonQObjectCreationSharedData() + { + if (reference != nullptr) { + Shiboken::GilState gil; + Py_DECREF(reference); } + } +#else + = default; +#endif + + PyObject *callable{}; // Callback, static method or type object to be invoked. + PyObject *reference{}; // Object to dereference when going out scope +}; + +// Base class for QML singleton creation callbacks with helper for error checking. +class SingletonQObjectCreationBase +{ +protected: + explicit SingletonQObjectCreationBase(PyObject *cb, PyObject *ref = nullptr) : + m_data(std::make_shared(cb, ref)) + { + } + + static QObject *handleReturnValue(PyObject *retVal); + + std::shared_ptr data() const { return m_data; } + +private: + std::shared_ptr m_data; +}; + +QObject *SingletonQObjectCreationBase::handleReturnValue(PyObject *retVal) +{ + using Shiboken::Conversions::isPythonToCppPointerConvertible; + // Make sure the callback returns something we can convert, else the entire application will crash. + if (retVal == nullptr) { + PyErr_Format(PyExc_TypeError, "Callback returns 0 value."); + return nullptr; + } + if (isPythonToCppPointerConvertible(qObjectType(), retVal) == nullptr) { + PyErr_Format(PyExc_TypeError, "Callback returns invalid value (%S).", retVal); + return nullptr; + } + + QObject *obj = nullptr; + Shiboken::Conversions::pythonToCppPointer(qObjectType(), retVal, &obj); + return obj; +} + +// QML singleton creation callback by invoking a type object +class SingletonQObjectFromTypeCreation : public SingletonQObjectCreationBase +{ +public: + explicit SingletonQObjectFromTypeCreation(PyObject *typeObj) : + SingletonQObjectCreationBase(typeObj, typeObj) {} + + QObject *operator ()(QQmlEngine *, QJSEngine *) const + { + Shiboken::GilState gil; + Shiboken::AutoDecRef args(PyTuple_New(0)); + PyObject *retVal = PyObject_CallObject(data()->callable, args); + QObject *result = handleReturnValue(retVal); + if (result == nullptr) + Py_XDECREF(retVal); + return result; + } +}; + +// QML singleton creation by invoking a callback, passing QQmlEngine. Keeps a +// references to the the callback. +class SingletonQObjectCallbackCreation : public SingletonQObjectCreationBase +{ +public: + explicit SingletonQObjectCallbackCreation(PyObject *callback) : + SingletonQObjectCreationBase(callback, callback) {} + explicit SingletonQObjectCallbackCreation(PyObject *callback, PyObject *ref) : + SingletonQObjectCreationBase(callback, ref) {} + + QObject *operator ()(QQmlEngine *engine, QJSEngine *) const + { + Shiboken::GilState gil; + Shiboken::AutoDecRef args(PyTuple_New(1)); + PyTuple_SET_ITEM(args, 0, + Shiboken::Conversions::pointerToPython(qQmlEngineType(), engine)); + PyObject *retVal = PyObject_CallObject(data()->callable, args); + QObject *result = handleReturnValue(retVal); + if (result == nullptr) + Py_XDECREF(retVal); + return result; + } +}; + +using SingletonQObjectCreation = std::function; + +// Modern (6.7) singleton type registration using RegisterSingletonTypeAndRevisions +// and information set to QMetaClassInfo (QObject only pending QTBUG-110467). +static int qmlRegisterSingletonTypeV2(PyObject *pyObj, PyObject *pyClassInfoObj, + const ImportData &importData, + const SingletonQObjectCreation &callback) +{ + PyTypeObject *pyObjType = reinterpret_cast(pyObj); + if (!isQObjectDerived(pyObjType, true)) + return -1; - // Make sure the callback never gets deallocated - Py_INCREF(callback); + const QMetaObject *metaObject = PySide::retrieveMetaObject(pyObjType); + Q_ASSERT(metaObject); + const QMetaObject *classInfoMetaObject = pyObj == pyClassInfoObj + ? metaObject : PySide::retrieveMetaObject(pyClassInfoObj); + + QList ids; + QQmlPrivate::RegisterSingletonTypeAndRevisions type { + QQmlPrivate::RegisterType::StructVersion::Base, // structVersion + importData.importName.constData(), + importData.toTypeRevision(), // version + callback, // qObjectApi, + metaObject, + classInfoMetaObject, + QMetaType(QMetaType::QObjectStar), // typeId + nullptr, // extensionMetaObject + nullptr, // extensionObjectCreate + &ids + }; + + QQmlPrivate::qmlregister(QQmlPrivate::SingletonAndRevisionsRegistration, &type); + const int qmlTypeId = ids.value(0, -1); + if (qmlTypeId == -1) { + PyErr_Format(PyExc_TypeError, "Singleton QML meta type registration of \"%s\" failed.", + pyObjType->tp_name); } + return qmlTypeId; +} + +// Legacy (pre 6.7) singleton type registration using RegisterSingletonType +// for QObject and value types. Still used by qmlRegisterSingletonType() +// for the hypothetical case of a value type. +static int qmlRegisterSingletonType(PyObject *pyObj, const ImportData &importData, + const char *qmlName, PyObject *callback, + bool isQObject, bool hasCallback) +{ + if (hasCallback && !checkSingletonCallback(callback)) + return -1; const QMetaObject *metaObject = nullptr; @@ -202,18 +506,14 @@ int qmlRegisterSingletonType(PyObject *pyObj, const char *uri, int versionMajor, if (!isQObjectDerived(pyObjType, true)) return -1; - // If we don't have a callback we'll need the pyObj to stay allocated indefinitely - if (!hasCallback) - Py_INCREF(pyObj); - metaObject = PySide::retrieveMetaObject(pyObjType); Q_ASSERT(metaObject); } QQmlPrivate::RegisterSingletonType type { QQmlPrivate::RegisterType::StructVersion::Base, // structVersion - uri, - QTypeRevision::fromVersion(versionMajor, versionMinor), // version + importData.importName.constData(), + importData.toTypeRevision(), // version qmlName, // typeName {}, // scriptApi {}, // qObjectApi @@ -228,36 +528,15 @@ int qmlRegisterSingletonType(PyObject *pyObj, const char *uri, int versionMajor, // FIXME: Fix this to assign new type ids each time. type.typeId = QMetaType(QMetaType::QObjectStar); - type.qObjectApi = - [callback, pyObj, hasCallback](QQmlEngine *engine, QJSEngine *) -> QObject * { - Shiboken::GilState gil; - AutoDecRef args(PyTuple_New(hasCallback ? 1 : 0)); - - if (hasCallback) { - PyTuple_SET_ITEM(args, 0, Conversions::pointerToPython( - qQmlEngineType(), engine)); - } - - AutoDecRef retVal(PyObject_CallObject(hasCallback ? callback : pyObj, args)); - - // Make sure the callback returns something we can convert, else the entire application will crash. - if (retVal.isNull() || - Conversions::isPythonToCppPointerConvertible(qObjectType(), retVal) == nullptr) { - PyErr_Format(PyExc_TypeError, "Callback returns invalid value."); - return nullptr; - } - - QObject *obj = nullptr; - Conversions::pythonToCppPointer(qObjectType(), retVal, &obj); - - if (obj != nullptr) - Py_INCREF(retVal); - - return obj; - }; + if (hasCallback) + type.qObjectApi = SingletonQObjectCallbackCreation(callback); + else + type.qObjectApi = SingletonQObjectFromTypeCreation(pyObj); } else { type.scriptApi = [callback](QQmlEngine *engine, QJSEngine *) -> QJSValue { + using namespace Shiboken; + Shiboken::GilState gil; AutoDecRef args(PyTuple_New(1)); @@ -287,9 +566,19 @@ int qmlRegisterSingletonType(PyObject *pyObj, const char *uri, int versionMajor, return QQmlPrivate::qmlregister(QQmlPrivate::SingletonRegistration, &type); } -int qmlRegisterSingletonInstance(PyObject *pyObj, const char *uri, int versionMajor, - int versionMinor, const char *qmlName, - PyObject *instanceObject) +// Legacy (pre 6.7) compatibility helper for the free register functions. +int qmlRegisterSingletonType(PyObject *pyObj,const char *uri, + int versionMajor, int versionMinor, const char *qmlName, + PyObject *callback, bool isQObject, bool hasCallback) +{ + return qmlRegisterSingletonType(pyObj, {uri, versionMajor, versionMinor}, qmlName, + callback, isQObject, hasCallback); +} + +// Modern (6.7) singleton instance registration using RegisterSingletonTypeAndRevisions +// and information set to QMetaClassInfo (QObject only). +static int qmlRegisterSingletonInstance(PyObject *pyObj, const ImportData &importData, + PyObject *instanceObject) { using namespace Shiboken; @@ -312,169 +601,140 @@ int qmlRegisterSingletonInstance(PyObject *pyObj, const char *uri, int versionMa const QMetaObject *metaObject = PySide::retrieveMetaObject(pyObjType); Q_ASSERT(metaObject); - // FIXME: Fix this to assign new type ids each time. - const QMetaType typeId = QMetaType(QMetaType::QObjectStar); - - QQmlPrivate::RegisterSingletonType type { + QList ids; + QQmlPrivate::RegisterSingletonTypeAndRevisions type { QQmlPrivate::RegisterType::StructVersion::Base, // structVersion - uri, - QTypeRevision::fromVersion(versionMajor, versionMinor), // version - qmlName, // typeName - {}, // scriptApi - registrationFunctor, // qObjectApi - metaObject, // instanceMetaObject - typeId, + importData.importName.constData(), + importData.toTypeRevision(), // version + registrationFunctor, // qObjectApi, + metaObject, + metaObject, // classInfoMetaObject + QMetaType(QMetaType::QObjectStar), // typeId nullptr, // extensionMetaObject nullptr, // extensionObjectCreate - {} // revision + &ids }; - return QQmlPrivate::qmlregister(QQmlPrivate::SingletonRegistration, &type); -} - -} // namespace PySide::Qml - -static std::string getGlobalString(const char *name) -{ - using Shiboken::AutoDecRef; - - PyObject *globals = PyEval_GetGlobals(); - - AutoDecRef pyName(Py_BuildValue("s", name)); - - PyObject *globalVar = PyDict_GetItem(globals, pyName); - - if (globalVar == nullptr || !PyUnicode_Check(globalVar)) - return ""; - - const char *stringValue = _PepUnicode_AsString(globalVar); - return stringValue != nullptr ? stringValue : ""; + QQmlPrivate::qmlregister(QQmlPrivate::SingletonAndRevisionsRegistration, &type); + return ids.value(0, -1); } -static int getGlobalInt(const char *name) +// Legacy (pre 6.7) compatibility helper for the free register functions. +int qmlRegisterSingletonInstance(PyObject *pyObj, const char *uri, int versionMajor, + int versionMinor, const char *qmlName, + PyObject *instanceObject) { - using Shiboken::AutoDecRef; - - PyObject *globals = PyEval_GetGlobals(); - - AutoDecRef pyName(Py_BuildValue("s", name)); - - PyObject *globalVar = PyDict_GetItem(globals, pyName); - - if (globalVar == nullptr || !PyLong_Check(globalVar)) + auto *type = checkTypeObject(pyObj, "qmlRegisterSingletonInstance()"); + if (type == nullptr || !setClassInfo(type, qmlElementKey, qmlName) + || !setSingletonClassInfo(type)) { return -1; - - long value = PyLong_AsLong(globalVar); - - if (value > std::numeric_limits::max() || value < std::numeric_limits::min()) - return -1; - - return value; + } + return qmlRegisterSingletonInstance(pyObj, {uri, versionMajor, versionMinor}, + instanceObject); } +} // namespace PySide::Qml + enum class RegisterMode { Normal, - Anonymous, - Uncreatable, Singleton }; -static PyObject *qmlElementMacroHelper(PyObject *pyObj, - const char *decoratorName, - const char *typeName = nullptr, - RegisterMode mode = RegisterMode::Normal, - const char *noCreationReason = nullptr) +namespace PySide::Qml { + +// Check for a static create() method on a decorated singleton. +// Might set a Python error if the check fails. +static std::optional + singletonCreateMethod(PyTypeObject *pyObjType) { - if (!PyType_Check(pyObj)) { - PyErr_Format(PyExc_TypeError, "This decorator can only be used on classes."); - return nullptr; + Shiboken::AutoDecRef tpDict(PepType_GetDict(pyObjType)); + auto *create = PyDict_GetItemString(tpDict.object(), "create"); + // Method decorated by "@staticmethod" + if (create == nullptr || std::strcmp(Py_TYPE(create)->tp_name, "staticmethod") != 0) + return std::nullopt; + // 3.10: "__wrapped__" + Shiboken::AutoDecRef function(PyObject_GetAttrString(create, "__func__")); + if (function.isNull()) { + PyErr_Format(PyExc_TypeError, "Cannot retrieve function of callback (%S).", + create); + return std::nullopt; } + if (!checkSingletonCallback(function.object())) + return std::nullopt; + // Reference to the type needs to be kept. + return SingletonQObjectCallbackCreation(function.object(), + reinterpret_cast(pyObjType)); +} - PyTypeObject *pyObjType = reinterpret_cast(pyObj); - if (typeName == nullptr) - typeName = pyObjType->tp_name; - if (!PySequence_Contains(pyObjType->tp_mro, reinterpret_cast(qObjectType()))) { - PyErr_Format(PyExc_TypeError, "This decorator can only be used with classes inherited from QObject, got %s.", - typeName); +PyObject *qmlElementMacro(PyObject *pyObj, const char *decoratorName, + const QByteArray &typeName) +{ + auto *pyObjType = checkTypeObject(pyObj, decoratorName); + if (pyObjType == nullptr) return nullptr; - } - - std::string importName = getGlobalString("QML_IMPORT_NAME"); - int majorVersion = getGlobalInt("QML_IMPORT_MAJOR_VERSION"); - int minorVersion = getGlobalInt("QML_IMPORT_MINOR_VERSION"); - if (importName.empty()) { - PyErr_Format(PyExc_TypeError, "You need specify QML_IMPORT_NAME in order to use %s.", - decoratorName); + if (!PySide::isQObjectDerived(pyObjType, false)) { + PyErr_Format(PyExc_TypeError, + "%s can only be used with classes inherited from QObject, got %s.", + decoratorName, pyObjType->tp_name); return nullptr; } - if (majorVersion == -1) { - PyErr_Format(PyExc_TypeError, "You need specify QML_IMPORT_MAJOR_VERSION in order to use %s.", - decoratorName); - return nullptr; - } - - // Specifying a minor version is optional - if (minorVersion == -1) - minorVersion = 0; - - const char *uri = importName.c_str(); - const int result = mode == RegisterMode::Singleton - ? PySide::Qml::qmlRegisterSingletonType(pyObj, uri, majorVersion, minorVersion, - typeName, nullptr, - PySide::isQObjectDerived(pyObjType, false), - false) - : PySide::Qml::qmlRegisterType(pyObj, uri, majorVersion, minorVersion, - mode != RegisterMode::Anonymous ? typeName : nullptr, - noCreationReason, - mode == RegisterMode::Normal); - - if (result == -1) { - PyErr_Format(PyExc_TypeError, "%s: Failed to register type %s.", - decoratorName, typeName); - } - - return pyObj; -} - -namespace PySide::Qml { + if (!setClassInfo(pyObjType, qmlElementKey, typeName)) + return nullptr; -PyObject *qmlElementMacro(PyObject *pyObj, const char *decoratorName, - const char *typeName = nullptr) -{ RegisterMode mode = RegisterMode::Normal; - const char *noCreationReason = nullptr; const auto info = PySide::Qml::qmlTypeInfo(pyObj); auto *registerObject = pyObj; if (info) { - if (info->flags.testFlag(PySide::Qml::QmlTypeFlag::Singleton)) + if (info->flags.testFlag(PySide::Qml::QmlTypeFlag::Singleton)) { mode = RegisterMode::Singleton; - else if (info->flags.testFlag(PySide::Qml::QmlTypeFlag::Uncreatable)) - mode = RegisterMode::Uncreatable; - noCreationReason = info->noCreationReason.c_str(); + setSingletonClassInfo(pyObjType); + } if (info->foreignType) registerObject = reinterpret_cast(info->foreignType); } - if (!qmlElementMacroHelper(registerObject, decoratorName, typeName, mode, noCreationReason)) + + const auto importDataO = getGlobalImportData(decoratorName); + if (!importDataO.has_value()) + return nullptr; + const auto importData = importDataO.value(); + + int result{}; + if (mode == RegisterMode::Singleton) { + auto singletonCreateMethodO = singletonCreateMethod(pyObjType); + if (!singletonCreateMethodO.has_value()) { + if (PyErr_Occurred() != nullptr) + return nullptr; + singletonCreateMethodO = SingletonQObjectFromTypeCreation(pyObj); + } + result = PySide::Qml::qmlRegisterSingletonTypeV2(registerObject, pyObj, importData, + singletonCreateMethodO.value()); + } else { + result = PySide::Qml::qmlRegisterType(registerObject, pyObj, importData); + } + if (result == -1) { + PyErr_Format(PyExc_TypeError, "%s: Failed to register type %s.", + decoratorName, pyObjType->tp_name); return nullptr; + } + return pyObj; } PyObject *qmlElementMacro(PyObject *pyObj) { - return qmlElementMacro(pyObj, "QmlElement"); + return qmlElementMacro(pyObj, "QmlElement", "auto"_ba); } -PyObject *qmlNamedElementMacro(PyObject *pyObj, const char *typeName) +PyObject *qmlNamedElementMacro(PyObject *pyObj, const QByteArray &typeName) { - return qmlElementMacro(pyObj, "QmlNamedElement", qstrdup(typeName)); + return qmlElementMacro(pyObj, "QmlNamedElement", typeName); } PyObject *qmlAnonymousMacro(PyObject *pyObj) { - return qmlElementMacroHelper(pyObj, "QmlAnonymous", nullptr, - RegisterMode::Anonymous); + return qmlElementMacro(pyObj, "QmlAnonymous", "anonymous"_ba); } PyObject *qmlSingletonMacro(PyObject *pyObj) diff --git a/sources/pyside6/libpysideqml/pysideqmlregistertype.h b/sources/pyside6/libpysideqml/pysideqmlregistertype.h index 8d0e8065..85917232 100644 --- a/sources/pyside6/libpysideqml/pysideqmlregistertype.h +++ b/sources/pyside6/libpysideqml/pysideqmlregistertype.h @@ -12,7 +12,7 @@ QT_BEGIN_NAMESPACE namespace QQmlPrivate { -struct RegisterType; +struct RegisterTypeAndRevisions; } QT_END_NAMESPACE @@ -22,6 +22,9 @@ namespace PySide::Qml /** * PySide implementation of qmlRegisterType function. * + * This is a helper for the legacy free qmlRegisterType*() type functions. + * Decorators should be used instead. + * * \param pyObj Python type to be registered. * \param uri QML element uri. * \param versionMajor QML component major version. @@ -37,6 +40,9 @@ PYSIDEQML_API int qmlRegisterType(PyObject *pyObj, const char *uri, /** * PySide implementation of qmlRegisterSingletonType function. * + * This is a helper for the legacy free qmlRegisterSingletonType type function. + * Decorators should be used instead. + * * \param pyObj Python type to be registered. * \param uri QML element uri. * \param versionMajor QML component major version. @@ -83,7 +89,7 @@ PYSIDEQML_API PyObject *qmlSingletonMacro(PyObject *pyObj); // Used by QtQuick module to fill the QQmlPrivate::RegisterType::parserStatusCast, // valueSourceCast and valueInterceptorCast fields with the correct values. using QuickRegisterItemFunction = - bool (*)(PyObject *pyObj, QT_PREPEND_NAMESPACE(QQmlPrivate::RegisterType) *); + bool (*)(PyObject *pyObj, QT_PREPEND_NAMESPACE(QQmlPrivate::RegisterTypeAndRevisions) *); PYSIDEQML_API QuickRegisterItemFunction getQuickRegisterItemFunction(); PYSIDEQML_API void setQuickRegisterItemFunction(QuickRegisterItemFunction function); diff --git a/sources/pyside6/libpysideqml/pysideqmlregistertype_p.h b/sources/pyside6/libpysideqml/pysideqmlregistertype_p.h index 3f077ee2..f11f9224 100644 --- a/sources/pyside6/libpysideqml/pysideqmlregistertype_p.h +++ b/sources/pyside6/libpysideqml/pysideqmlregistertype_p.h @@ -6,12 +6,14 @@ #include +#include + PyTypeObject *qObjectType(); namespace PySide::Qml { -PyObject *qmlNamedElementMacro(PyObject *pyObj, const char *typeName); +PyObject *qmlNamedElementMacro(PyObject *pyObj, const QByteArray &typeName); } diff --git a/sources/pyside6/libpysideqml/pysideqmltypeinfo.cpp b/sources/pyside6/libpysideqml/pysideqmltypeinfo.cpp index 0304d33e..f369f740 100644 --- a/sources/pyside6/libpysideqml/pysideqmltypeinfo.cpp +++ b/sources/pyside6/libpysideqml/pysideqmltypeinfo.cpp @@ -42,8 +42,6 @@ QDebug operator<<(QDebug d, const QmlTypeInfo &i) d.noquote(); d.nospace(); d << "QmlTypeInfo(" << i.flags; - if (!i.noCreationReason.empty()) - d << ", noCreationReason=\"" << i.noCreationReason.c_str() << '"'; if (i.foreignType) d << ", foreignType=" << i.foreignType->tp_name; if (i.attachedType) diff --git a/sources/pyside6/libpysideqml/pysideqmltypeinfo_p.h b/sources/pyside6/libpysideqml/pysideqmltypeinfo_p.h index e10a2b38..112e127a 100644 --- a/sources/pyside6/libpysideqml/pysideqmltypeinfo_p.h +++ b/sources/pyside6/libpysideqml/pysideqmltypeinfo_p.h @@ -6,10 +6,10 @@ #include +#include #include #include -#include QT_FORWARD_DECLARE_CLASS(QDebug) QT_FORWARD_DECLARE_CLASS(QObject) @@ -19,8 +19,7 @@ namespace PySide::Qml { enum class QmlTypeFlag { - Singleton = 0x1, - Uncreatable = 0x2 + Singleton = 0x1 }; Q_DECLARE_FLAGS(QmlTypeFlags, QmlTypeFlag) @@ -30,7 +29,6 @@ Q_DECLARE_OPERATORS_FOR_FLAGS(QmlTypeFlags) struct QmlTypeInfo { QmlTypeFlags flags; - std::string noCreationReason; PyTypeObject *foreignType = nullptr; PyTypeObject *attachedType = nullptr; PyTypeObject *extensionType = nullptr; diff --git a/sources/pyside6/libpysideqml/pysideqmluncreatable.cpp b/sources/pyside6/libpysideqml/pysideqmluncreatable.cpp index 43c795cb..7c0f6b8f 100644 --- a/sources/pyside6/libpysideqml/pysideqmluncreatable.cpp +++ b/sources/pyside6/libpysideqml/pysideqmluncreatable.cpp @@ -2,17 +2,17 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "pysideqmluncreatable.h" -#include "pysideqmltypeinfo_p.h" #include +#include #include #include #include -#include -#include +#include +#include -#include +using namespace Qt::StringLiterals; class PySideQmlUncreatablePrivate : public PySide::ClassDecorator::StringDecoratorPrivate { @@ -35,11 +35,9 @@ PyObject *PySideQmlUncreatablePrivate::tp_call(PyObject *self, PyObject *args, P if (klass== nullptr) return nullptr; + auto *type = reinterpret_cast(klass); auto *data = DecoratorPrivate::get(self); - - const auto info = PySide::Qml::ensureQmlTypeInfo(klass); - info->flags.setFlag(PySide::Qml::QmlTypeFlag::Uncreatable); - info->noCreationReason = data->string(); + setUncreatableClassInfo(type, data->string()); Py_INCREF(klass); return klass; @@ -105,3 +103,16 @@ void initQmlUncreatable(PyObject *module) PyModule_AddObject(module, "QmlUncreatable", reinterpret_cast(PySideQmlUncreatable_TypeF())); } + +void setUncreatableClassInfo(PyTypeObject *type, const QByteArray &reason) +{ + PySide::ClassInfo::setClassInfo(type, { + {"QML.Creatable"_ba, "false"_ba}, + {"QML.UncreatableReason"_ba, reason} }); +} + +void setUncreatableClassInfo(QMetaObjectBuilder *builder, const QByteArray &reason) +{ + builder->addClassInfo("QML.Creatable", "false"); + builder->addClassInfo("QML.UncreatableReason", reason); +} diff --git a/sources/pyside6/libpysideqml/pysideqmluncreatable.h b/sources/pyside6/libpysideqml/pysideqmluncreatable.h index 7961634c..8a8adb3c 100644 --- a/sources/pyside6/libpysideqml/pysideqmluncreatable.h +++ b/sources/pyside6/libpysideqml/pysideqmluncreatable.h @@ -6,6 +6,10 @@ #include +#include + +QT_FORWARD_DECLARE_CLASS(QMetaObjectBuilder) + // The QmlUncreatable decorator modifies QmlElement to register an uncreatable // type. Due to the (reverse) execution order of decorators, it needs to follow // QmlElement. @@ -16,4 +20,7 @@ extern "C" void initQmlUncreatable(PyObject *module); +void setUncreatableClassInfo(PyTypeObject *type, const QByteArray &reason); +void setUncreatableClassInfo(QMetaObjectBuilder *builder, const QByteArray &reason); + #endif // PYSIDEQMLUNCREATABLE_H diff --git a/sources/pyside6/plugins/designer/CMakeLists.txt b/sources/pyside6/plugins/designer/CMakeLists.txt index c86d328c..71765231 100644 --- a/sources/pyside6/plugins/designer/CMakeLists.txt +++ b/sources/pyside6/plugins/designer/CMakeLists.txt @@ -19,6 +19,9 @@ target_sources(PySidePlugin PRIVATE ) # See libshiboken/CMakeLists.txt + +target_compile_definitions(PySidePlugin PRIVATE -DQT_NO_KEYWORDS=1) + if(PYTHON_LIMITED_API) target_compile_definitions(PySidePlugin PRIVATE "-DPy_LIMITED_API=0x03050000") endif() diff --git a/sources/pyside6/plugins/designer/designercustomwidgets.cpp b/sources/pyside6/plugins/designer/designercustomwidgets.cpp index ccff635a..d23156a9 100644 --- a/sources/pyside6/plugins/designer/designercustomwidgets.cpp +++ b/sources/pyside6/plugins/designer/designercustomwidgets.cpp @@ -55,7 +55,7 @@ static QString pyStr(PyObject *o) static QString pyErrorMessage() { - QString result = QLatin1String(""); + QString result = ""_L1; PyObject *ptype = {}; PyObject *pvalue = {}; PyObject *ptraceback = {}; @@ -87,7 +87,7 @@ static bool runPyScript(const char *script, QString *errorMessage) { PyObject *main = PyImport_AddModule("__main__"); if (main == nullptr) { - *errorMessage = QLatin1String("Internal error: Cannot retrieve __main__"); + *errorMessage = "Internal error: Cannot retrieve __main__"_L1; return false; } PyObject *globalDictionary = PyModule_GetDict(main); @@ -118,8 +118,7 @@ static bool runPyScriptFile(const QString &fileName, QString *errorMessage) file.close(); const bool ok = runPyScript(script.constData(), errorMessage); if (!ok && !errorMessage->isEmpty()) { - errorMessage->prepend(QLatin1String("Error running ") + fileName - + QLatin1String(": ")); + errorMessage->prepend("Error running "_L1 + fileName + ": "_L1); } return ok; } diff --git a/sources/pyside6/plugins/uitools/customwidget.h b/sources/pyside6/plugins/uitools/customwidget.h index bfc17ffb..52621f0b 100644 --- a/sources/pyside6/plugins/uitools/customwidget.h +++ b/sources/pyside6/plugins/uitools/customwidget.h @@ -8,8 +8,6 @@ #include -#include - class PyCustomWidget: public QObject, public QDesignerCustomWidgetInterface { Q_OBJECT diff --git a/sources/pyside6/tests/CMakeLists.txt b/sources/pyside6/tests/CMakeLists.txt index c581a8d0..539e1aea 100644 --- a/sources/pyside6/tests/CMakeLists.txt +++ b/sources/pyside6/tests/CMakeLists.txt @@ -17,7 +17,7 @@ endif() # the path to the testbinding module get_filename_component(BUILD_DIR "${CMAKE_BINARY_DIR}" DIRECTORY) get_filename_component(BUILD_DIR "${CMAKE_BINARY_DIR}" DIRECTORY) -set(QT_DIR "${_qt5Core_install_prefix}") +set(QT_DIR "${QT6_INSTALL_PREFIX}") macro(TEST_QT_MODULE var name) if(NOT DISABLE_${name} AND ${var}) diff --git a/sources/pyside6/tests/QtAsyncio/qasyncio_test_cancel_taskgroup.py b/sources/pyside6/tests/QtAsyncio/qasyncio_test_cancel_taskgroup.py new file mode 100644 index 00000000..aa8ce471 --- /dev/null +++ b/sources/pyside6/tests/QtAsyncio/qasyncio_test_cancel_taskgroup.py @@ -0,0 +1,57 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +'''Test cases for QtAsyncio''' + +import asyncio +import unittest + +import PySide6.QtAsyncio as QtAsyncio + + +class QAsyncioTestCaseCancelTaskGroup(unittest.TestCase): + def setUp(self) -> None: + super().setUp() + # We only reach the end of the loop if the task is not cancelled. + self.loop_end_reached = False + + async def raise_error(self): + raise RuntimeError + + async def loop_short(self): + self._loop_end_reached = False + for _ in range(1000): + await asyncio.sleep(1e-3) + self._loop_end_reached = True + + async def loop_shorter(self): + self._loop_end_reached = False + for _ in range(1000): + await asyncio.sleep(1e-4) + self._loop_end_reached = True + + async def loop_the_shortest(self): + self._loop_end_reached = False + for _ in range(1000): + await asyncio.to_thread(lambda: None) + self._loop_end_reached = True + + async def main(self, coro): + async with asyncio.TaskGroup() as tg: + tg.create_task(coro()) + tg.create_task(self.raise_error()) + + def test_cancel_taskgroup(self): + coros = [self.loop_short, self.loop_shorter, self.loop_the_shortest] + + for coro in coros: + try: + QtAsyncio.run(self.main(coro), keep_running=False) + except ExceptionGroup as e: + self.assertEqual(len(e.exceptions), 1) + self.assertIsInstance(e.exceptions[0], RuntimeError) + self.assertFalse(self._loop_end_reached) + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/QtCore/CMakeLists.txt b/sources/pyside6/tests/QtCore/CMakeLists.txt index f584bfde..f0228d94 100644 --- a/sources/pyside6/tests/QtCore/CMakeLists.txt +++ b/sources/pyside6/tests/QtCore/CMakeLists.txt @@ -34,6 +34,7 @@ PYSIDE_TEST(bug_1313.py) PYSIDE_TEST(bug_PYSIDE-41.py) PYSIDE_TEST(bug_PYSIDE-42.py) PYSIDE_TEST(bug_PYSIDE-164.py) +PYSIDE_TEST(bug_PYSIDE-2745.py) PYSIDE_TEST(blocking_signals_test.py) PYSIDE_TEST(classinfo_test.py) PYSIDE_TEST(child_event_test.py) diff --git a/sources/pyside6/tests/QtCore/blocking_signals_test.py b/sources/pyside6/tests/QtCore/blocking_signals_test.py index d8159c2e..493abb07 100644 --- a/sources/pyside6/tests/QtCore/blocking_signals_test.py +++ b/sources/pyside6/tests/QtCore/blocking_signals_test.py @@ -14,7 +14,12 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from init_paths import init_test_paths init_test_paths(False) -from PySide6.QtCore import QObject, SIGNAL, QFile, QSignalBlocker +from PySide6.QtCore import QObject, Signal, QFile, QSignalBlocker + + +class Sender(QObject): + mysignal = Signal() + mysignal_int_int = Signal(int, int) class TestSignalsBlockedBasic(unittest.TestCase): @@ -61,7 +66,7 @@ class TestSignalsBlocked(unittest.TestCase): def setUp(self): # Set up the basic resources needed - self.obj = QObject() + self.obj = Sender() self.args = tuple() self.called = False @@ -81,27 +86,28 @@ class TestSignalsBlocked(unittest.TestCase): def testShortCircuitSignals(self): # Blocking of Python short-circuit signals - QObject.connect(self.obj, SIGNAL('mysignal()'), self.callback) + self.obj.mysignal.connect(self.callback) - self.obj.emit(SIGNAL('mysignal()')) + self.obj.mysignal.emit() self.assertTrue(self.called) self.called = False self.obj.blockSignals(True) - self.obj.emit(SIGNAL('mysignal()')) + self.obj.mysignal.emit() self.assertTrue(not self.called) def testPythonSignals(self): # Blocking of Python typed signals - QObject.connect(self.obj, SIGNAL('mysignal(int,int)'), self.callback) + + self.obj.mysignal_int_int.connect(self.callback) self.args = (1, 3) - self.obj.emit(SIGNAL('mysignal(int,int)'), *self.args) + self.obj.mysignal_int_int.emit(*self.args) self.assertTrue(self.called) self.called = False self.obj.blockSignals(True) - self.obj.emit(SIGNAL('mysignal(int,int)'), *self.args) + self.obj.mysignal_int_int.emit(*self.args) self.assertTrue(not self.called) @@ -130,7 +136,7 @@ class TestQFileSignalBlocking(unittest.TestCase): def testAboutToCloseBlocking(self): # QIODevice.aboutToClose() blocking - QObject.connect(self.qfile, SIGNAL('aboutToClose()'), self.callback) + self.qfile.aboutToClose.connect(self.callback) self.assertTrue(self.qfile.open(QFile.ReadOnly)) self.qfile.close() diff --git a/sources/pyside6/tests/QtCore/bug_927.py b/sources/pyside6/tests/QtCore/bug_927.py index 1ecea61b..c15a7014 100644 --- a/sources/pyside6/tests/QtCore/bug_927.py +++ b/sources/pyside6/tests/QtCore/bug_927.py @@ -24,7 +24,7 @@ class thread_function(): class Task(QRunnable): def run(self): - QThread.sleep(2) # Sleep 2 seconds + QThread.msleep(100) class QThreadPoolTest(unittest.TestCase): @@ -34,15 +34,15 @@ class QThreadPoolTest(unittest.TestCase): for i in range(3): task = Task() QThreadPool.globalInstance().start(task) - time.sleep(1) # Sleep 1 second + time.sleep(0.05) - QThreadPool.globalInstance().waitForDone() + self.assertTrue(QThreadPool.globalInstance().waitForDone()) def testCallable(self): global thread_function_called tp = QThreadPool.globalInstance() tp.start(thread_function) - tp.waitForDone() + self.assertTrue(tp.waitForDone()) self.assertTrue(thread_function_called) diff --git a/sources/pyside6/tests/QtCore/bug_987.py b/sources/pyside6/tests/QtCore/bug_987.py index bc92e573..a8c9799e 100644 --- a/sources/pyside6/tests/QtCore/bug_987.py +++ b/sources/pyside6/tests/QtCore/bug_987.py @@ -19,7 +19,7 @@ class TestBug987(unittest.TestCase): def testInvalidDisconnection(self): o = QObject() - self.assertRaises(RuntimeError, o.destroyed.disconnect, self.callback) + self.assertFalse(o.destroyed.disconnect(self.callback)) if __name__ == '__main__': diff --git a/sources/pyside6/tests/QtCore/bug_PYSIDE-164.py b/sources/pyside6/tests/QtCore/bug_PYSIDE-164.py index c5a6736a..84859af8 100644 --- a/sources/pyside6/tests/QtCore/bug_PYSIDE-164.py +++ b/sources/pyside6/tests/QtCore/bug_PYSIDE-164.py @@ -10,16 +10,19 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from init_paths import init_test_paths init_test_paths(False) -from PySide6.QtCore import QCoreApplication, QEventLoop, QObject, Qt, QThread, QTimer, SIGNAL +from PySide6.QtCore import QCoreApplication, QEventLoop, QObject, Qt, QThread, Signal class Emitter(QThread): + + signal = Signal(int) + def __init__(self): super().__init__() def run(self): print("Before emit.") - self.emit(SIGNAL("signal(int)"), 0) + self.signal.emit(0) print("After emit.") @@ -36,12 +39,11 @@ class Receiver(QObject): class TestBugPYSIDE164(unittest.TestCase): def testBlockingSignal(self): - app = QCoreApplication.instance() or QCoreApplication([]) + app = QCoreApplication.instance() or QCoreApplication([]) # noqa: F841 eventloop = QEventLoop() emitter = Emitter() receiver = Receiver(eventloop) - emitter.connect(emitter, SIGNAL("signal(int)"), - receiver.receive, Qt.BlockingQueuedConnection) + emitter.signal.connect(receiver.receive, Qt.BlockingQueuedConnection) emitter.start() retval = eventloop.exec() emitter.wait(2000) diff --git a/sources/pyside6/tests/QtCore/bug_PYSIDE-2745.py b/sources/pyside6/tests/QtCore/bug_PYSIDE-2745.py new file mode 100644 index 00000000..3d6c603b --- /dev/null +++ b/sources/pyside6/tests/QtCore/bug_PYSIDE-2745.py @@ -0,0 +1,37 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import os +import sys +import unittest + +from pathlib import Path +sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) +from init_paths import init_test_paths +init_test_paths(False) + +from PySide6.QtCore import QTimer + +from helper.usesqapplication import UsesQApplication + + +class TestBugPYSIDE2745(UsesQApplication): + + def setUp(self): + UsesQApplication.setUp(self) + self.counter = 0 + + def fail(self): + self.counter += 1 + raise Exception() + + def test_fail(self): + QTimer.singleShot(0, self.fail) + QTimer.singleShot(0, self.fail) + QTimer.singleShot(1, self.app.quit) + self.app.exec() + self.assertEqual(self.counter, 2) + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/QtCore/errormessages_with_features_test.py b/sources/pyside6/tests/QtCore/errormessages_with_features_test.py index 8cb38882..97c4f942 100644 --- a/sources/pyside6/tests/QtCore/errormessages_with_features_test.py +++ b/sources/pyside6/tests/QtCore/errormessages_with_features_test.py @@ -34,7 +34,6 @@ This test is in its own file because combining it with @unittest.skipIf(is_pypy, "__feature__ cannot yet be used with PyPy") class ErrormessagesWithFeatures(unittest.TestCase): probe = "called with wrong argument types" - probe_miss = "missing signature" def setUp(self): qApp or QApplication() @@ -76,20 +75,20 @@ class ErrormessagesWithFeatures(unittest.TestCase): with self.assertRaises(TypeError) as cm: QApplication.quitOnLastWindowClosed = object print("\n\n" + cm.exception.args[0]) - self.assertTrue(self.probe_miss in cm.exception.args[0]) + self.assertTrue(self.probe in cm.exception.args[0]) with self.assertRaises(TypeError) as cm: qApp.quitOnLastWindowClosed = object - self.assertTrue(self.probe_miss in cm.exception.args[0]) + self.assertTrue(self.probe in cm.exception.args[0]) def testCorrectErrorMessagesClassSnakeProp(self): from __feature__ import snake_case, true_property with self.assertRaises(TypeError) as cm: QApplication.quit_on_last_window_closed = object print("\n\n" + cm.exception.args[0]) - self.assertTrue(self.probe_miss in cm.exception.args[0]) + self.assertTrue(self.probe in cm.exception.args[0]) with self.assertRaises(TypeError) as cm: qApp.quit_on_last_window_closed = object - self.assertTrue(self.probe_miss in cm.exception.args[0]) + self.assertTrue(self.probe in cm.exception.args[0]) def testDocIsWorking(self): """ diff --git a/sources/pyside6/tests/QtCore/hash_test.py b/sources/pyside6/tests/QtCore/hash_test.py index acd10078..aee2f516 100644 --- a/sources/pyside6/tests/QtCore/hash_test.py +++ b/sources/pyside6/tests/QtCore/hash_test.py @@ -15,13 +15,17 @@ from PySide6.QtCore import QDate, QDateTime, QTime, QUrl from PySide6.QtCore import QLine, QPoint, QRect, QSize +URL = "https://qt.io/" + + class HashTest(unittest.TestCase): def testInsert(self): myHash = {} qdate = QDate.currentDate() qdatetime = QDateTime.currentDateTime() qtime = QTime.currentTime() - qurl = QUrl("http://www.pyside.org") + qurl = QUrl(URL) + self.assertTrue(qurl.isValid()) qpoint = QPoint(12, 42) myHash[qdate] = "QDate" @@ -64,7 +68,34 @@ class HashTest(unittest.TestCase): self.assertEqual(l1, l2) self.assertEqual(hash(l1), hash(l2)) + def testQTimeHash(self): + t1 = QTime(5, 5, 5) + t2 = QTime(5, 5, 5) + self.assertFalse(t1 is t2) + self.assertEqual(t1, t2) + self.assertEqual(hash(t1), hash(t2)) + + def testQDateHash(self): + d1 = QDate(1968, 3, 9) + d2 = QDate(1968, 3, 9) + self.assertFalse(d1 is d2) + self.assertEqual(d1, d2) + self.assertEqual(hash(d1), hash(d2)) + + def testQDateTimeHash(self): + d1 = QDateTime(QDate(1968, 3, 9), QTime(5, 5, 5)) + d2 = QDateTime(QDate(1968, 3, 9), QTime(5, 5, 5)) + self.assertFalse(d1 is d2) + self.assertEqual(d1, d2) + self.assertEqual(hash(d1), hash(d2)) + + def testQUrlHash(self): + u1 = QUrl(URL) + u2 = QUrl(URL) + self.assertFalse(u1 is u2) + self.assertEqual(u1, u2) + self.assertEqual(hash(u1), hash(u2)) + if __name__ == '__main__': unittest.main() - diff --git a/sources/pyside6/tests/QtCore/qbytearray_test.py b/sources/pyside6/tests/QtCore/qbytearray_test.py index c0067485..cb8f9a43 100644 --- a/sources/pyside6/tests/QtCore/qbytearray_test.py +++ b/sources/pyside6/tests/QtCore/qbytearray_test.py @@ -6,6 +6,7 @@ import ctypes import os import pickle +import struct import sys import unittest @@ -15,7 +16,8 @@ from init_paths import init_test_paths init_test_paths(False) -from PySide6.QtCore import QByteArray, QSettings, QObject, QDataStream, QIODevice +from PySide6.QtCore import (QByteArray, QSettings, QObject, QDataStream, + QIODevice, qCompress, qUncompress) class QByteArrayTestToNumber(unittest.TestCase): @@ -255,6 +257,34 @@ class QByteArraySliceAssignment(unittest.TestCase): actual_bytes = bytes(byte_array) self.assertEqual(orig_bytes, actual_bytes) + def testUnpack(self): + b = QByteArray(b'\x19\x00\x00\x00\xc4\t\x00\x00') + t = struct.unpack(' None: + self.pipe = QIOPipe() + self.pipe.open(QIODevice.OpenModeFlag.ReadWrite) + return super().setUp() + + def tearDown(self) -> None: + super().tearDown() + + def ready_read_bytes_written(self): + received_data = self.pipe.end2().readAll().data() + self.assertEqual(received_data, self.data) + + def test_readyRead(self): + self.data = b"Hello, World!" + self.pipe.end2().readyRead.connect(self.ready_read_bytes_written) + self.pipe.end1().write(self.data) + + def test_bytesWritten(self): + self.data = b"Hello, World!" + self.pipe.end2().bytesWritten.connect(self.ready_read_bytes_written) + self.pipe.end1().write(self.data) + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/QtCore/qlockfile_test.py b/sources/pyside6/tests/QtCore/qlockfile_test.py index 106bca02..6dca9235 100644 --- a/sources/pyside6/tests/QtCore/qlockfile_test.py +++ b/sources/pyside6/tests/QtCore/qlockfile_test.py @@ -32,6 +32,9 @@ class TestQMessageAuthenticationCode (unittest.TestCase): lockFile = QLockFile(self._fileName) self.assertTrue(lockFile.lock()) self.assertTrue(lockFile.isLocked()) + lock_info = lockFile.getLockInfo(); + self.assertEqual(len(lock_info), 3) + self.assertEqual(lock_info[0], os.getpid()) lockFile.unlock() diff --git a/sources/pyside6/tests/QtCore/qmetaobject_test.py b/sources/pyside6/tests/QtCore/qmetaobject_test.py index 8f6467a0..4b305171 100644 --- a/sources/pyside6/tests/QtCore/qmetaobject_test.py +++ b/sources/pyside6/tests/QtCore/qmetaobject_test.py @@ -105,15 +105,6 @@ class qmetaobject_test(unittest.TestCase): o.connect(o2, SIGNAL("bars()"), o.slot) self.assertTrue(o2.metaObject().indexOfMethod("bars()") > -1) - #self.assertTrue(o.metaObject().indexOfMethod("bar()") == -1) - #self.assertTrue(o.metaObject().indexOfMethod("slot()") > -1) - - #slot_index = o.metaObject().indexOfMethod("slot()") - - #o.connect(o, SIGNAL("foo()"), o2, SIGNAL("bar()")) - #signal_index = o.metaObject().indexOfMethod("foo()"); - - #self.assertTrue(slot_index != signal_index) # PYSIDE-784, plain Qt objects should not have intermediary # metaObjects. @@ -125,7 +116,7 @@ class qmetaobject_test(unittest.TestCase): # PYSIDE-1827, slots with non-QObject object types should work # (metatypes are registered) def test_ObjectSlotSignal(self): - app = QCoreApplication() + app = QCoreApplication() # noqa: F841 sender = SemaphoreSender() receiver = SemaphoreReceiver() sender.signal.connect(receiver.receiverSlot, Qt.QueuedConnection) diff --git a/sources/pyside6/tests/QtCore/qmimedatabase_test.py b/sources/pyside6/tests/QtCore/qmimedatabase_test.py index 90c99edb..6a63edb8 100644 --- a/sources/pyside6/tests/QtCore/qmimedatabase_test.py +++ b/sources/pyside6/tests/QtCore/qmimedatabase_test.py @@ -4,7 +4,6 @@ '''Unit tests for QMimeDatabase''' -import ctypes import os import sys import unittest @@ -24,8 +23,7 @@ class QMimeDatabaseTest(unittest.TestCase): s0 = db.mimeTypeForName("application/x-zerosize") self.assertTrue(s0.isValid()) self.assertEqual(s0.name(), "application/x-zerosize") - if "en" in QLocale().name(): - self.assertEqual(s0.comment(), "empty document") + self.assertTrue(s0.comment()) s0Again = db.mimeTypeForName("application/x-zerosize") self.assertEqual(s0Again.name(), s0.name()) @@ -40,13 +38,13 @@ class QMimeDatabaseTest(unittest.TestCase): rdf = db.mimeTypeForName("application/rdf+xml") self.assertTrue(rdf.isValid()) self.assertEqual(rdf.name(), "application/rdf+xml") + self.assertTrue(rdf.comment()) if "en" in QLocale().name(): self.assertEqual(rdf.comment(), "RDF file") bzip2 = db.mimeTypeForName("application/x-bzip2") self.assertTrue(bzip2.isValid()) - if "en" in QLocale().name(): - self.assertEqual(bzip2.comment(), "Bzip archive") + self.assertTrue(bzip2.comment()) defaultMime = db.mimeTypeForName("application/octet-stream") self.assertTrue(defaultMime.isValid()) diff --git a/sources/pyside6/tests/QtCore/qobject_connect_notify_test.py b/sources/pyside6/tests/QtCore/qobject_connect_notify_test.py index b93408d1..b3bfaf00 100644 --- a/sources/pyside6/tests/QtCore/qobject_connect_notify_test.py +++ b/sources/pyside6/tests/QtCore/qobject_connect_notify_test.py @@ -12,7 +12,7 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from init_paths import init_test_paths init_test_paths(False) -from PySide6.QtCore import QObject, SIGNAL, SLOT +from PySide6.QtCore import QObject, Signal, SIGNAL, SLOT from helper.usesqapplication import UsesQApplication @@ -21,6 +21,9 @@ def cute_slot(): class Obj(QObject): + + foo = Signal() + def __init__(self): super().__init__() self.con_notified = False @@ -49,6 +52,15 @@ class TestQObjectConnectNotify(UsesQApplication): UsesQApplication.tearDown(self) def testBasic(self): + sender = Obj() + receiver = QObject() + sender.destroyed.connect(receiver.deleteLater) + self.assertTrue(sender.con_notified) + self.assertEqual(sender.signal.methodSignature(), "destroyed()") + self.assertTrue(sender.destroyed.disconnect(receiver.deleteLater)) + self.assertTrue(sender.dis_notified) + + def testBasicString(self): sender = Obj() receiver = QObject() sender.connect(SIGNAL("destroyed()"), receiver, SLOT("deleteLater()")) @@ -57,31 +69,29 @@ class TestQObjectConnectNotify(UsesQApplication): # will use the non-cloned method signature, so connecting to destroyed() will actually # connect to destroyed(QObject*). self.assertEqual(sender.signal.methodSignature(), "destroyed(QObject*)") - sender.disconnect(SIGNAL("destroyed()"), receiver, SLOT("deleteLater()")) + self.assertTrue(sender.disconnect(SIGNAL("destroyed()"), receiver, SLOT("deleteLater()"))) self.assertTrue(sender.dis_notified) def testPySignal(self): sender = Obj() receiver = QObject() - sender.connect(SIGNAL("foo()"), receiver, SLOT("deleteLater()")) + sender.foo.connect(receiver.deleteLater) self.assertTrue(sender.con_notified) - sender.disconnect(SIGNAL("foo()"), receiver, SLOT("deleteLater()")) + self.assertTrue(sender.foo.disconnect(receiver.deleteLater)) self.assertTrue(sender.dis_notified) def testPySlots(self): sender = Obj() - receiver = QObject() - sender.connect(SIGNAL("destroyed()"), cute_slot) + sender.destroyed.connect(cute_slot) self.assertTrue(sender.con_notified) - sender.disconnect(SIGNAL("destroyed()"), cute_slot) + self.assertTrue(sender.destroyed.disconnect(cute_slot)) self.assertTrue(sender.dis_notified) def testpyAll(self): sender = Obj() - receiver = QObject() - sender.connect(SIGNAL("foo()"), cute_slot) + sender.foo.connect(cute_slot) self.assertTrue(sender.con_notified) - sender.disconnect(SIGNAL("foo()"), cute_slot) + self.assertTrue(sender.foo.disconnect(cute_slot)) self.assertTrue(sender.dis_notified) diff --git a/sources/pyside6/tests/QtCore/qobject_event_filter_test.py b/sources/pyside6/tests/QtCore/qobject_event_filter_test.py index 1cf07fba..ab7a1b6a 100644 --- a/sources/pyside6/tests/QtCore/qobject_event_filter_test.py +++ b/sources/pyside6/tests/QtCore/qobject_event_filter_test.py @@ -63,6 +63,17 @@ class FilteredObject(QObject): self.app.quit() +class PolymorphicIdFilterObject(QObject): + """PYSIDE-2675: Check whether QChildEvent.added() is accessible via PolymorphicId""" + def __init__(self, parent=None): + super().__init__(parent) + self.added = False + + def event(self, event): + self.added = event.added() + return False + + class TestQObjectEventFilterPython(UsesQApplication): '''QObject.eventFilter - Reimplemented in python Filters 5 TimerEvents and then bypasses the other events to the @@ -93,6 +104,11 @@ class TestQObjectEventFilterPython(UsesQApplication): self.assertEqual(filtered.times_called, 5) self.assertEqual(self.obj_filter.events_handled, 5) + def testPolymorphicId(self): + testObject = PolymorphicIdFilterObject() + t2 = QObject(testObject) # noqa: F841 + self.assertTrue(testObject.added) + @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount") def testInstallEventFilterRefCountAfterDelete(self): '''Bug 910 - installEventFilter() increments reference count on target object diff --git a/sources/pyside6/tests/QtCore/qslot_object_test.py b/sources/pyside6/tests/QtCore/qslot_object_test.py index 061ce916..a95afb09 100644 --- a/sources/pyside6/tests/QtCore/qslot_object_test.py +++ b/sources/pyside6/tests/QtCore/qslot_object_test.py @@ -11,7 +11,7 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from init_paths import init_test_paths init_test_paths(False) -from PySide6.QtCore import QCoreApplication, QObject, QTimer, SIGNAL, SLOT +from PySide6.QtCore import QCoreApplication, QObject, QTimer """ This is a simple slot test that was updated to use the qApp "macro". @@ -28,24 +28,23 @@ class objTest(QObject): def slot(self): self.ok = True - qApp.quit() + qApp.quit() # noqa: F821 class slotTest(unittest.TestCase): def quit_app(self): - qApp.quit() + qApp.quit() # noqa: F821 def testBasic(self): timer = QTimer() timer.setInterval(100) my_obj = objTest() - my_slot = SLOT("slot()") - QObject.connect(timer, SIGNAL("timeout()"), my_obj, my_slot) + timer.timeout.connect(my_obj.slot) timer.start(100) QTimer.singleShot(1000, self.quit_app) - qApp.exec() + qApp.exec() # noqa: F821 self.assertTrue(my_obj.ok) diff --git a/sources/pyside6/tests/QtCore/qtimer_singleshot_test.py b/sources/pyside6/tests/QtCore/qtimer_singleshot_test.py index 9718a242..2ccaa300 100644 --- a/sources/pyside6/tests/QtCore/qtimer_singleshot_test.py +++ b/sources/pyside6/tests/QtCore/qtimer_singleshot_test.py @@ -14,7 +14,7 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from init_paths import init_test_paths init_test_paths(False) -from PySide6.QtCore import QObject, QThread, QTimer, Signal +from PySide6.QtCore import QObject, QThread, QTimer, Signal, Slot, SLOT from helper.usesqapplication import UsesQApplication @@ -74,6 +74,11 @@ class TestSingleShot(UsesQApplication): self.app.exec() self.assertTrue(self.called) + def testSingleShotZero(self): + QTimer.singleShot(0, self.callback) + self.app.exec() + self.assertTrue(self.called) + def testSingleShotWithContext(self): thread = ThreadForContext() thread.start() @@ -97,6 +102,56 @@ class TestSingleShot(UsesQApplication): self.assertEqual(self.qthread, thread.qthread) +class TestSingleShotCallableObject(UsesQApplication): + '''Test case for QTimer.singleShot with callable inside an object''' + + def setUp(self): + # Acquire resources + UsesQApplication.setUp(self) + self.watchdog = WatchDog(self) + + def tearDown(self): + # Release resources + del self.watchdog + # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion + gc.collect() + UsesQApplication.tearDown(self) + + class CallbackObject(QObject): + def __init__(self, app) -> None: + super().__init__() + self.app = app + + @Slot() + def func(self): + self.called = True + self.app.quit() + + def testSingleShotWithObjectAndMember(self): + callback = self.CallbackObject(self.app) + QTimer.singleShot(100, callback, SLOT("func()")) + self.app.exec() + self.assertTrue(callback.called) + + def testSingleShotWithObjectAndMemberZero(self): + callback = self.CallbackObject(self.app) + QTimer.singleShot(0, callback, SLOT("func()")) + self.app.exec() + self.assertTrue(callback.called) + + def testSingleShotWithCallableInObject(self): + callback = self.CallbackObject(self.app) + QTimer.singleShot(100, callback.func) + self.app.exec() + self.assertTrue(callback.called) + + def testSingleShotWithCallableInObjectZero(self): + callback = self.CallbackObject(self.app) + QTimer.singleShot(0, callback.func) + self.app.exec() + self.assertTrue(callback.called) + + class SigEmitter(QObject): sig1 = Signal() @@ -128,6 +183,13 @@ class TestSingleShotSignal(UsesQApplication): self.app.exec() self.assertTrue(self.called) + def testSingleShotSignalZero(self): + emitter = SigEmitter() + emitter.sig1.connect(self.callback) + QTimer.singleShot(0, emitter.sig1) + self.app.exec() + self.assertTrue(self.called) + if __name__ == '__main__': unittest.main() diff --git a/sources/pyside6/tests/QtCore/thread_signals_test.py b/sources/pyside6/tests/QtCore/thread_signals_test.py index b0a5f018..a453d1ac 100644 --- a/sources/pyside6/tests/QtCore/thread_signals_test.py +++ b/sources/pyside6/tests/QtCore/thread_signals_test.py @@ -12,14 +12,16 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from init_paths import init_test_paths init_test_paths(False) -from PySide6.QtCore import QObject, SIGNAL, QFile, QThread, QTimer, Qt +from PySide6.QtCore import Signal, QThread from helper.usesqapplication import UsesQApplication class MyThread(QThread): + test = Signal(str) + def run(self): - self.emit(SIGNAL("test(const QString&)"), "INdT - PySide") + self.test.emit("INdT - PySide") class TestThreadSignal(UsesQApplication): @@ -33,7 +35,7 @@ class TestThreadSignal(UsesQApplication): def testThread(self): t = MyThread() - QObject.connect(t, SIGNAL("test(const QString&)"), self._callback) + t.test.connect(self._callback) t.start() self.app.exec() diff --git a/sources/pyside6/tests/QtGui/qtransform_test.py b/sources/pyside6/tests/QtGui/qtransform_test.py index 053f0733..dc51a74e 100644 --- a/sources/pyside6/tests/QtGui/qtransform_test.py +++ b/sources/pyside6/tests/QtGui/qtransform_test.py @@ -11,7 +11,7 @@ from init_paths import init_test_paths init_test_paths(False) from PySide6.QtCore import QPointF -from PySide6.QtGui import QTransform, QPolygonF, QPolygonF +from PySide6.QtGui import QTransform, QPolygonF, QPolygonF, QQuaternion, QVector3D class QTransformTest(unittest.TestCase): @@ -78,6 +78,12 @@ class QTransformTest(unittest.TestCase): self.assertEqual(t1, r2) + def testQQuaternion(self): + """Test return tuples.""" + q = QQuaternion(1, 1, 1, 1) + self.assertEqual(len(q.getAxisAndAngle()), 2) + self.assertEqual(len(q.getEulerAngles()), 3) + if __name__ == "__main__": unittest.main() diff --git a/sources/pyside6/tests/QtGui/timed_app_and_patching_test.py b/sources/pyside6/tests/QtGui/timed_app_and_patching_test.py index c45d762b..6e9a661c 100644 --- a/sources/pyside6/tests/QtGui/timed_app_and_patching_test.py +++ b/sources/pyside6/tests/QtGui/timed_app_and_patching_test.py @@ -7,13 +7,10 @@ import unittest from pathlib import Path sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) -from init_paths import init_test_paths +from init_paths import init_test_paths # noqa: E402 init_test_paths(False) from helper.timedqguiapplication import TimedQGuiApplication -from PySide6.support import deprecated -from PySide6.support.signature import importhandler -from PySide6 import QtGui class TestTimedApp(TimedQGuiApplication): @@ -23,21 +20,7 @@ class TestTimedApp(TimedQGuiApplication): # Simple test of TimedQGuiApplication self.app.exec() - -def fix_for_QtGui(QtGui): - QtGui.something = 42 - - -class TestPatchingFramework(unittest.TestCase): - """Simple test that verifies that deprecated.py works""" - - deprecated.fix_for_QtGui = fix_for_QtGui - - def test_patch_works(self): - something = "something" - self.assertFalse(hasattr(QtGui, something)) - importhandler.finish_import(QtGui) - self.assertTrue(hasattr(QtGui, something)) +# deprecated.py is no longer needed. if __name__ == '__main__': diff --git a/sources/pyside6/tests/QtQml/CMakeLists.txt b/sources/pyside6/tests/QtQml/CMakeLists.txt index 720f0ef9..30bf7e78 100644 --- a/sources/pyside6/tests/QtQml/CMakeLists.txt +++ b/sources/pyside6/tests/QtQml/CMakeLists.txt @@ -17,6 +17,7 @@ PYSIDE_TEST(bug_997.py) PYSIDE_TEST(bug_1029.py) PYSIDE_TEST(groupedproperty.py) PYSIDE_TEST(listproperty.py) +PYSIDE_TEST(qmlregistertype_test.py) PYSIDE_TEST(qqmlapplicationengine_test.py) PYSIDE_TEST(qqmlnetwork_test.py) PYSIDE_TEST(qqmlcomponent_test.py) diff --git a/sources/pyside6/tests/QtQml/listproperty.py b/sources/pyside6/tests/QtQml/listproperty.py index 8916aefe..884600d2 100644 --- a/sources/pyside6/tests/QtQml/listproperty.py +++ b/sources/pyside6/tests/QtQml/listproperty.py @@ -7,11 +7,25 @@ import unittest from pathlib import Path sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) -from init_paths import init_test_paths +from init_paths import init_test_paths # noqa: E402 init_test_paths(False) -from PySide6.QtCore import QObject -from PySide6.QtQml import ListProperty +from helper.usesqapplication import UsesQApplication # noqa: E402, F401 + +from PySide6.QtCore import QObject, QUrl, Property, qInstallMessageHandler # noqa: E402 +from PySide6.QtQml import ListProperty, QmlElement # noqa: E402 +from PySide6.QtQuick import QQuickView # noqa: E402 + + +QML_IMPORT_NAME = "test.ListPropertyTest" +QML_IMPORT_MAJOR_VERSION = 1 + +output_messages = [] + + +def message_handler(mode, context, message): + global output_messages + output_messages.append(f"{message}") class InheritsQObject(QObject): @@ -22,7 +36,46 @@ def dummyFunc(): pass -class TestListProperty(unittest.TestCase): +@QmlElement +class Person(QObject): + def __init__(self, parent=None): + super().__init__(parent=None) + self._name = '' + self._friends = [] + + def appendFriend(self, friend): + self._friends.append(friend) + + def friendCount(self): + return len(self._friends) + + def friend(self, index): + return self._friends[index] + + def removeLastItem(self): + if len(self._friends) > 0: + self._friends.pop() + + def replace(self, index, friend): + if 0 <= index < len(self._friends): + self._friends[index] = friend + + def clear(self): + self._friends.clear() + + @Property(str, final=True) + def name(self): + return self._name + + @name.setter + def name(self, value): + self._name = value + + friends = ListProperty(QObject, append=appendFriend, count=friendCount, at=friend, + removeLast=removeLastItem, replace=replace, clear=clear) + + +class TestListProperty(UsesQApplication): def testIt(self): # Verify that type checking works properly @@ -31,7 +84,7 @@ class TestListProperty(unittest.TestCase): try: ListProperty(QObject) ListProperty(InheritsQObject) - except: + except Exception: type_check_error = True self.assertFalse(type_check_error) @@ -47,21 +100,37 @@ class TestListProperty(unittest.TestCase): method_check_error = False try: - ListProperty(QObject, append=None, at=None, count=None, replace=None, clear=None, removeLast=None) # Explicitly setting None + ListProperty(QObject, append=None, at=None, count=None, replace=None, clear=None, + removeLast=None) # Explicitly setting None ListProperty(QObject, append=dummyFunc) ListProperty(QObject, count=dummyFunc, at=dummyFunc) - except: + except Exception: method_check_error = True self.assertFalse(method_check_error) try: - ListPropery(QObject, append=QObject()) - except: + ListProperty(QObject, append=QObject()) + except Exception: method_check_error = True self.assertTrue(method_check_error) + def testListPropParameters(self): + global output_messages + qInstallMessageHandler(message_handler) + view = QQuickView() + file = Path(__file__).resolve().parent / 'listproperty.qml' + self.assertTrue(file.is_file()) + view.setSource(QUrl.fromLocalFile(file)) + view.show() + self.assertEqual(output_messages[0], "List length: 3") + self.assertEqual(output_messages[1], "First element: Alice") + self.assertEqual(output_messages[2], "Removing last item: Charlie") + self.assertEqual(output_messages[3], "Replacing last item: Bob") + self.assertEqual(output_messages[4], "Replaced last item: David") + self.assertEqual(output_messages[5], "List length after clearing: 0") + if __name__ == '__main__': unittest.main() diff --git a/sources/pyside6/tests/QtQml/listproperty.qml b/sources/pyside6/tests/QtQml/listproperty.qml new file mode 100644 index 00000000..7b71e30b --- /dev/null +++ b/sources/pyside6/tests/QtQml/listproperty.qml @@ -0,0 +1,50 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick 2.0 +import test.ListPropertyTest + +Rectangle { + width: 360 + height: 360 + + Person { + id: person + friends: [ + Person{ + name: "Alice" + }, + Person{ + name: "Bob" + }, + Person{ + name: "Charlie" + } + ] + } + + Person{ + id: david + name: "David" + } + + Component.onCompleted: { + // Access the length of the list + console.log("List length: " + person.friends.length); + + // Access the first element of the list + console.log("First element: " + person.friends[0].name); + + // Remove the last item of the list + console.log("Removing last item: " + person.friends.pop().name); + + // Repalce the last item of the list + console.log("Replacing last item: " + person.friends[person.friends.length - 1].name); + person.friends[person.friends.length - 1] = david; + console.log("Replaced last item: " + person.friends[person.friends.length - 1].name); + + // Clear the list + person.friends = []; + console.log("List length after clearing: " + person.friends.length); + } +} diff --git a/sources/pyside6/tests/QtQml/qmlregistertype_test.py b/sources/pyside6/tests/QtQml/qmlregistertype_test.py new file mode 100644 index 00000000..0042d6fd --- /dev/null +++ b/sources/pyside6/tests/QtQml/qmlregistertype_test.py @@ -0,0 +1,53 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import os +import sys +import unittest + +from pathlib import Path +sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) +from init_paths import init_test_paths +init_test_paths(False) + +from helper.usesqapplication import UsesQApplication + + +from PySide6.QtCore import QCoreApplication, QObject # noqa: F401 +from PySide6.QtQml import QQmlApplicationEngine, qmlRegisterType + + +class BaseClass(QObject): + def __init__(self, p=None): + super().__init__(p) + + +class ChildClass(BaseClass): + def __init__(self, p=None): + super().__init__(p) + + +class TestQmlRegisterType(UsesQApplication): + """Test the legacy QML register functions.""" + + def test(self): + qmlRegisterType(BaseClass, 'test', 1, 0, 'BaseClass') + qmlRegisterType(ChildClass, 'test', 1, 0, 'ChildClass') + # PYSIDE-2709: qmlRegisterType() would set additional class info + # on the meta objects for registration which caused another meta + # object to be created, breaking inheritance. + child = ChildClass() + base = BaseClass() + self.assertTrue(child.metaObject().inherits(base.metaObject())) + + engine = QQmlApplicationEngine() + file = Path(__file__).resolve().parent / 'qmlregistertype_test.qml' + + engine.load(file) + rootObjects = engine.rootObjects() + self.assertTrue(rootObjects) + self.assertTrue(type(rootObjects[0]), ChildClass) + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/QtQml/qmlregistertype_test.qml b/sources/pyside6/tests/QtQml/qmlregistertype_test.qml new file mode 100644 index 00000000..108bb84b --- /dev/null +++ b/sources/pyside6/tests/QtQml/qmlregistertype_test.qml @@ -0,0 +1,7 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import test + +ChildClass { +} diff --git a/sources/pyside6/tests/QtQml/registersingletontype.py b/sources/pyside6/tests/QtQml/registersingletontype.py index 492217fd..6beca113 100644 --- a/sources/pyside6/tests/QtQml/registersingletontype.py +++ b/sources/pyside6/tests/QtQml/registersingletontype.py @@ -12,13 +12,18 @@ init_test_paths(False) from helper.helper import quickview_errorstring -from PySide6.QtCore import Property, Signal, QTimer, QUrl, QObject +from PySide6.QtCore import Property, Signal, QTimer, QUrl, QObject, Slot from PySide6.QtGui import QGuiApplication from PySide6.QtQml import (qmlRegisterSingletonType, qmlRegisterSingletonInstance, - QmlElement, QmlSingleton) + QmlElement, QmlSingleton, QJSValue) from PySide6.QtQuick import QQuickView + +URI = "Singletons" + + finalResult = 0 +qObjectQmlTypeId = 0 class SingletonQObject(QObject): @@ -46,7 +51,7 @@ def singletonQJSValueCallback(engine): return engine.evaluate("new Object({data: 50})") -QML_IMPORT_NAME = "Singletons" +QML_IMPORT_NAME = URI QML_IMPORT_MAJOR_VERSION = 1 @@ -66,37 +71,82 @@ class DecoratedSingletonQObject(QObject): data = Property(int, getData, setData) +@QmlElement +@QmlSingleton +class DecoratedSingletonWithCreate(QObject): + def __init__(self, data, parent=None): + super().__init__(parent) + self._data = data + + @staticmethod + def create(engine): + return DecoratedSingletonWithCreate(400) + + def getData(self): + return self._data + + def setData(self, data): + self._data = data + + data = Property(int, getData, setData) + + +class TestQuickView(QQuickView): + def __init__(self, parent=None): + super().__init__(parent) + self._singleton_instance_qobject_int = False + self._singleton_instance_qobject_str = False + self._singleton_instance_jsvalue_int = False + + @Slot() + def testSlot(self): + engine = self.engine() + instance = engine.singletonInstance(qObjectQmlTypeId) + if instance is not None and isinstance(instance, QObject): + self._singleton_instance_qobject_int = True + instance = engine.singletonInstance(URI, 'SingletonQObjectNoCallback') + if instance is not None and isinstance(instance, QObject): + self._singleton_instance_qobject_str = True + instance = engine.singletonInstance(URI, 'SingletonQJSValue') + if instance is not None and isinstance(instance, QJSValue): + self._singleton_instance_jsvalue_int = True + self.close() + + class TestQmlSupport(unittest.TestCase): def testIt(self): app = QGuiApplication([]) - qmlRegisterSingletonType(SingletonQObject, 'Singletons', 1, 0, 'SingletonQObjectNoCallback') - qmlRegisterSingletonType(SingletonQObject, 'Singletons', 1, 0, 'SingletonQObjectCallback', + qObjectQmlTypeId = qmlRegisterSingletonType(SingletonQObject, URI, 1, 0, + 'SingletonQObjectNoCallback') + qmlRegisterSingletonType(SingletonQObject, URI, 1, 0, 'SingletonQObjectCallback', singletonQObjectCallback) - qmlRegisterSingletonType('Singletons', 1, 0, 'SingletonQJSValue', singletonQJSValueCallback) + qmlRegisterSingletonType(URI, 1, 0, 'SingletonQJSValue', singletonQJSValueCallback) # Accepts only QObject derived types l = [1, 2] with self.assertRaises(TypeError): - qmlRegisterSingletonInstance(SingletonQObject, 'Singletons', 1, 0, 'SingletonInstance', l) + qmlRegisterSingletonInstance(SingletonQObject, URI, 1, 0, 'SingletonInstance', l) # Modify value on the instance s = SingletonQObject() s.setData(99) - qmlRegisterSingletonInstance(SingletonQObject, 'Singletons', 1, 0, 'SingletonInstance', s) + qmlRegisterSingletonInstance(SingletonQObject, URI, 1, 0, 'SingletonInstance', s) - view = QQuickView() + view = TestQuickView() file = Path(__file__).resolve().parent / 'registersingletontype.qml' self.assertTrue(file.is_file()) view.setSource(QUrl.fromLocalFile(file)) self.assertTrue(view.rootObject(), quickview_errorstring(view)) view.resize(200, 200) view.show() - QTimer.singleShot(250, view.close) + QTimer.singleShot(250, view.testSlot) app.exec() - self.assertEqual(finalResult, 499) + self.assertEqual(finalResult, 899) + self.assertTrue(view._singleton_instance_qobject_int) + self.assertTrue(view._singleton_instance_qobject_str) + self.assertTrue(view._singleton_instance_jsvalue_int) -if __name__ == '__main__': - unittest.main() +if __name__ == '__main__': unittest.main() diff --git a/sources/pyside6/tests/QtQml/registersingletontype.qml b/sources/pyside6/tests/QtQml/registersingletontype.qml index 4c45b198..31ca7fe4 100644 --- a/sources/pyside6/tests/QtQml/registersingletontype.qml +++ b/sources/pyside6/tests/QtQml/registersingletontype.qml @@ -9,6 +9,6 @@ Item { SingletonQObjectCallback.data += SingletonQObjectNoCallback.data + SingletonQJSValue.data + SingletonInstance.data - + DecoratedSingletonQObject.data; + + DecoratedSingletonQObject.data + DecoratedSingletonWithCreate.data; } } diff --git a/sources/pyside6/tests/QtQuickTest/CMakeLists.txt b/sources/pyside6/tests/QtQuickTest/CMakeLists.txt new file mode 100644 index 00000000..49f15e44 --- /dev/null +++ b/sources/pyside6/tests/QtQuickTest/CMakeLists.txt @@ -0,0 +1,3 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause +PYSIDE_TEST(quicktestmainwithsetup/tst_quicktestmainwithsetup.py) diff --git a/sources/pyside6/tests/QtQuickTest/quicktestmainwithsetup/data/tst_setup.qml b/sources/pyside6/tests/QtQuickTest/quicktestmainwithsetup/data/tst_setup.qml new file mode 100644 index 00000000..2cfe936a --- /dev/null +++ b/sources/pyside6/tests/QtQuickTest/quicktestmainwithsetup/data/tst_setup.qml @@ -0,0 +1,20 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick 2.0 +import QtTest 1.2 + +import QmlRegisterTypeCppModule 1.0 +import ImportPathQmlModule 1.0 + +TestCase { + name: "setup" + + QmlRegisterTypeCppType {} + ImportPathQmlType {} + + function initTestCase() + { + verify(qmlEngineAvailableCalled) + } +} diff --git a/sources/pyside6/tests/QtQuickTest/quicktestmainwithsetup/imports/ImportPathQmlModule/ImportPathQmlType.qml b/sources/pyside6/tests/QtQuickTest/quicktestmainwithsetup/imports/ImportPathQmlModule/ImportPathQmlType.qml new file mode 100644 index 00000000..617bdaaf --- /dev/null +++ b/sources/pyside6/tests/QtQuickTest/quicktestmainwithsetup/imports/ImportPathQmlModule/ImportPathQmlType.qml @@ -0,0 +1,3 @@ +import QtQuick 2.0 + +Item {} diff --git a/sources/pyside6/tests/QtQuickTest/quicktestmainwithsetup/imports/ImportPathQmlModule/qmldir b/sources/pyside6/tests/QtQuickTest/quicktestmainwithsetup/imports/ImportPathQmlModule/qmldir new file mode 100644 index 00000000..dea7c9a8 --- /dev/null +++ b/sources/pyside6/tests/QtQuickTest/quicktestmainwithsetup/imports/ImportPathQmlModule/qmldir @@ -0,0 +1,2 @@ +module ImportPathQmlModule +ImportPathQmlType 1.0 ImportPathQmlType.qml diff --git a/sources/pyside6/tests/QtQuickTest/quicktestmainwithsetup/quicktestmainwithsetup.pyproject b/sources/pyside6/tests/QtQuickTest/quicktestmainwithsetup/quicktestmainwithsetup.pyproject new file mode 100644 index 00000000..61e89f4a --- /dev/null +++ b/sources/pyside6/tests/QtQuickTest/quicktestmainwithsetup/quicktestmainwithsetup.pyproject @@ -0,0 +1,4 @@ +{ + "files": ["tst_quicktestmainwithsetup.py", "data/tst_setup.qml", + "imports/ImportPathQmlModule/ImportPathQmlType.qml"] +} diff --git a/sources/pyside6/tests/QtQuickTest/quicktestmainwithsetup/tst_quicktestmainwithsetup.py b/sources/pyside6/tests/QtQuickTest/quicktestmainwithsetup/tst_quicktestmainwithsetup.py new file mode 100644 index 00000000..33b2db08 --- /dev/null +++ b/sources/pyside6/tests/QtQuickTest/quicktestmainwithsetup/tst_quicktestmainwithsetup.py @@ -0,0 +1,46 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import os +import sys + +from pathlib import Path +sys.path.append(os.fspath(Path(__file__).resolve().parents[2])) +from init_paths import init_test_paths +init_test_paths(False) + +from pathlib import Path +from PySide6.QtCore import QObject, Slot +from PySide6.QtQml import QQmlEngine, qmlRegisterType +from PySide6.QtQuickTest import QUICK_TEST_MAIN_WITH_SETUP + + +"""Copy of the equivalent test in qtdeclarative.""" + + +class QmlRegisterTypeCppType(QObject): + def __init__(self, parent=None): + super().__init__(parent) + + +class CustomTestSetup(QObject): + def __init__(self, parent=None): + super().__init__(parent) + + @Slot(QQmlEngine) + def qmlEngineAvailable(self, qmlEngine): + # Test that modules are successfully imported by the TestCaseCollector + # that parses the QML files (but doesn't run them). For that to happen, + # qmlEngineAvailable() must be called before TestCaseCollector does its + # thing. + qmlRegisterType(QmlRegisterTypeCppType, "QmlRegisterTypeCppModule", 1, 0, + "QmlRegisterTypeCppType") + import_dir = Path(__file__).parent / "imports" + qmlEngine.addImportPath(os.fspath(import_dir)) + qmlEngine.rootContext().setContextProperty("qmlEngineAvailableCalled", True) + + +data_dir = Path(__file__).parent / "data" +exitCode = QUICK_TEST_MAIN_WITH_SETUP("qquicktestsetup", CustomTestSetup, sys.argv, + os.fspath(data_dir)) +sys.exit(exitCode) diff --git a/sources/pyside6/tests/QtWidgets/CMakeLists.txt b/sources/pyside6/tests/QtWidgets/CMakeLists.txt index 54e0d6c3..01b7d08e 100644 --- a/sources/pyside6/tests/QtWidgets/CMakeLists.txt +++ b/sources/pyside6/tests/QtWidgets/CMakeLists.txt @@ -84,6 +84,7 @@ PYSIDE_TEST(qapp_issue_585.py) PYSIDE_TEST(qapp_test.py) PYSIDE_TEST(qapplication_test.py) PYSIDE_TEST(qapplication_exit_segfault_test.py) +PYSIDE_TEST(qdialog_test.py) PYSIDE_TEST(qdynamic_signal.py) # TODO: This passes, but requires manual button clicking (at least on mac) #PYSIDE_TEST(qfontdialog_test.py) diff --git a/sources/pyside6/tests/QtWidgets/qdialog_test.py b/sources/pyside6/tests/QtWidgets/qdialog_test.py new file mode 100644 index 00000000..cb85ce7e --- /dev/null +++ b/sources/pyside6/tests/QtWidgets/qdialog_test.py @@ -0,0 +1,52 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import os +import sys +import unittest +import weakref + +from pathlib import Path +sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) +from init_paths import init_test_paths +init_test_paths(False) + +from PySide6.QtCore import Slot, QTimer +from PySide6.QtWidgets import QDialog, QMainWindow +from helper.timedqapplication import TimedQApplication + + +class Window(QMainWindow): + def __init__(self): + super().__init__() + self.setWindowTitle("Main") + self.dialog = None + + @Slot() + def execDialog(self): + dialog = QDialog(self) + self.dialog = weakref.ref(dialog) + dialog.setWindowTitle("Dialog") + dialog.setMinimumWidth(200) + QTimer.singleShot(500, dialog.reject) + dialog.exec() + self.close() + + +class DialogExecTest(TimedQApplication): + """Test whether the parent-child relationship (dialog/main window) is removed when + using QDialog.exec() (instead show()), preventing the dialog from leaking.""" + + def setUp(self): + super().setUp(10000) + self._window = Window() + + def testExec(self): + self._window.show() + QTimer.singleShot(500, self._window.execDialog) + self.app.exec() + self.assertTrue(self._window.dialog() is None) + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/manually/lazytiming.py b/sources/pyside6/tests/manually/lazytiming.py new file mode 100644 index 00000000..59f75bc4 --- /dev/null +++ b/sources/pyside6/tests/manually/lazytiming.py @@ -0,0 +1,38 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +""" +Time a repeated Python run +-------------------------- + +Usage: python3 lazytiming.py # uses PySide6 + python3 lazytiming.py # uses PyQt6 + +It runs the same python for the testing. + +Actually comparing PySide6 and PyQt6 in action: + + PYSIDE6_OPTION_LAZY=0 python3 sources/pyside6/tests/manually/lazytiming.py # normal + PYSIDE6_OPTION_LAZY=1 python3 sources/pyside6/tests/manually/lazytiming.py # faster + python3 sources/pyside6/tests/manually/lazytiming.py xxx # PyQt +""" +import subprocess +import sys + +from timeit import default_timer as timer + +repeats = 100 +test1 = "PySide6" +test2 = "PyQt6" + +test = test2 if sys.argv[1:] else test1 +cmd = [sys.executable, "-c", f"from {test} import QtCore, QtGui, QtWidgets"] + +print(f"{repeats} * {test}") + +subprocess.call(cmd) # warmup +start_time = timer() +for idx in range(repeats): + subprocess.call(cmd) +stop_time = timer() +print(f"time per run = {(stop_time - start_time) / repeats}") diff --git a/sources/pyside6/tests/pysidetest/CMakeLists.txt b/sources/pyside6/tests/pysidetest/CMakeLists.txt index 6dc56735..38f42f34 100644 --- a/sources/pyside6/tests/pysidetest/CMakeLists.txt +++ b/sources/pyside6/tests/pysidetest/CMakeLists.txt @@ -46,6 +46,7 @@ ${CMAKE_CURRENT_BINARY_DIR}/testbinding/sharedpointertestbench_wrapper.cpp ${CMAKE_CURRENT_BINARY_DIR}/testbinding/testview_wrapper.cpp ${CMAKE_CURRENT_BINARY_DIR}/testbinding/testbinding_module_wrapper.cpp ${CMAKE_CURRENT_BINARY_DIR}/testbinding/testqvariantenum_wrapper.cpp +${CMAKE_CURRENT_BINARY_DIR}/testbinding/qvariantholder_wrapper.cpp ) # Get per module include dirs. diff --git a/sources/pyside6/tests/pysidetest/multiple_inheritance_test.py b/sources/pyside6/tests/pysidetest/multiple_inheritance_test.py index fe8e14f9..49550ba5 100644 --- a/sources/pyside6/tests/pysidetest/multiple_inheritance_test.py +++ b/sources/pyside6/tests/pysidetest/multiple_inheritance_test.py @@ -12,7 +12,7 @@ init_test_paths(False) from helper.usesqapplication import UsesQApplication from PySide6 import QtCore, QtGui, QtWidgets -from PySide6.QtWidgets import QMainWindow, QLabel +from PySide6.QtWidgets import QMainWindow, QLabel, QWidget def xprint(*args, **kw): @@ -71,7 +71,8 @@ class C(A, B): xprint('C: after init') -# mro ('F', 'D', 'QCursor', 'E', 'QLabel', 'QFrame', 'QWidget', 'QObject', 'QPaintDevice', 'Object', 'object') +# mro ('F', 'D', 'QCursor', 'E', 'QLabel', 'QFrame', 'QWidget', 'QObject', +# 'QPaintDevice', 'Object', 'object') class D(QtGui.QCursor): def __init__(self, anna=77, **kw): xprint(f'D: before init kw = {kw}') @@ -94,7 +95,8 @@ class F(D, E, QtWidgets.QLabel): xprint('F: after init') -# mro ('I', 'G', 'QTextDocument', 'H', 'QLabel', 'QFrame', 'QWidget', 'QObject', 'QPaintDevice', 'Object', 'object') +# mro ('I', 'G', 'QTextDocument', 'H', 'QLabel', 'QFrame', 'QWidget', 'QObject', +# 'QPaintDevice', 'Object', 'object') # Similar, but this time we want to reach `H` without support from `super`. class G(QtGui.QTextDocument): pass @@ -108,7 +110,7 @@ class H: xprint('H: after init') -class I(G, H, QtWidgets.QLabel): +class II(G, H, QtWidgets.QLabel): pass @@ -145,7 +147,7 @@ class AdditionalMultipleInheritanceTest(UsesQApplication): def testGHI(self): xprint() - res = I(age=7) + res = II(age=7) self.assertEqual(res.age, 7) xprint() @@ -155,5 +157,33 @@ class AdditionalMultipleInheritanceTest(UsesQApplication): MainWindow() +# PYSIDE-2654: Additional missing init test. +# This must work if no __init__ is defined (Ui_Form) +class Ui_Form(object): + pass + + +class Mixin: + def __init__(self, **kwargs) -> None: + super().__init__(**kwargs) + + +class Card(Mixin, QWidget): + def __init__(self, parent=None) -> None: + super().__init__(parent=parent) + + +class Demo(Card, Ui_Form): + def __init__(self) -> None: + super().__init__() + + +class MissingInitFunctionTest(UsesQApplication): + def testMissing(self): + Demo() + # Tests if this works. Would crash without the extra + # check for object.__init__ + + if __name__ == "__main__": unittest.main() diff --git a/sources/pyside6/tests/pysidetest/qvariant_test.py b/sources/pyside6/tests/pysidetest/qvariant_test.py index 79ac446e..faefc816 100644 --- a/sources/pyside6/tests/pysidetest/qvariant_test.py +++ b/sources/pyside6/tests/pysidetest/qvariant_test.py @@ -26,8 +26,8 @@ class PyTestQVariantEnum(TestQVariantEnum): return Qt.Orientation.Vertical def channelingEnum(self, rval_enum): - return (isinstance(rval_enum, enum.Enum) and - rval_enum == Qt.Orientation.Vertical) + return (isinstance(rval_enum, enum.Enum) + and rval_enum == Qt.Orientation.Vertical) class QVariantTest(UsesQApplication): @@ -43,8 +43,6 @@ class QVariantTest(UsesQApplication): QAction().setShortcut(Qt.CTRL | Qt.AltModifier | Qt.Key_B) QAction().setShortcut(QKeySequence(QKeyCombination(Qt.CTRL | Qt.Key_B))) QKeySequence(Qt.CTRL | Qt.Key_Q) - # Issues a warning but works as well - QKeySequence(Qt.CTRL + Qt.Key_Q) def testEnum(self): # Testing C++ class diff --git a/sources/pyside6/tests/pysidetest/repr_test.py b/sources/pyside6/tests/pysidetest/repr_test.py index 01cc36b3..863f1765 100644 --- a/sources/pyside6/tests/pysidetest/repr_test.py +++ b/sources/pyside6/tests/pysidetest/repr_test.py @@ -49,6 +49,15 @@ class QObjectDerivedReprTest(unittest.TestCase): # __repr__ should use the operator<<(QDebug,...) implementation self.assertEqual(str(t), "TestObject2WithNamespace(injected_repr)") + def testLatin1StringField(self): + self.assertEqual(TestObject.LATIN1_TEST_FIELD, "test") + + def testLatin1Setter(self): + to = TestObject(123) + value = "test" + to.setQLatin1String(value) + self.assertEqual(to.qLatin1String(), value) + if __name__ == '__main__': unittest.main() diff --git a/sources/pyside6/tests/pysidetest/signalwithdefaultvalue_test.py b/sources/pyside6/tests/pysidetest/signalwithdefaultvalue_test.py index cf09b296..744b8c50 100644 --- a/sources/pyside6/tests/pysidetest/signalwithdefaultvalue_test.py +++ b/sources/pyside6/tests/pysidetest/signalwithdefaultvalue_test.py @@ -13,7 +13,7 @@ from init_paths import init_test_paths init_test_paths(True) from testbinding import TestObject -from PySide6.QtCore import QObject, SIGNAL +from PySide6.QtCore import Qt '''Tests the behaviour of signals with default values.''' @@ -56,6 +56,17 @@ class SignalWithDefaultValueTest(unittest.TestCase): self.assertTrue(self.void_called) self.assertTrue(self.bool_called) + def testFlagsSignal(self): + test_value = Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignBottom + + def callbackAlignmentFlags(alignment): + self.alignment_flags_called = alignment + + self.obj.flagsSignal.connect(callbackAlignmentFlags) + self.obj.emitFlagsSignal(test_value) + self.assertTrue(self.alignment_flags_called) + self.assertEqual(self.alignment_flags_called, test_value) + def testConnectOldStyleEmitVoidSignal(self): def callbackVoid(): self.void_called = True @@ -83,4 +94,3 @@ class SignalWithDefaultValueTest(unittest.TestCase): if __name__ == '__main__': unittest.main() - diff --git a/sources/pyside6/tests/pysidetest/testobject.cpp b/sources/pyside6/tests/pysidetest/testobject.cpp index 2233bad6..fe4ec98f 100644 --- a/sources/pyside6/tests/pysidetest/testobject.cpp +++ b/sources/pyside6/tests/pysidetest/testobject.cpp @@ -30,6 +30,26 @@ void TestObject::emitSignalWithTypedefValue(int value) emit signalWithTypedefValue(TypedefValue(value)); } +void TestObject::emitSignalWithContainerTypedefValue(const IntList &il) +{ + emit signalWithContainerTypedefValue(il); +} + +void TestObject::emitFlagsSignal(Qt::Alignment alignment) +{ + emit flagsSignal(alignment); +} + +void TestObject::setQLatin1String(QLatin1String v) +{ + m_qLatin1String = v; +} + +QString TestObject::qLatin1String() const +{ + return m_qLatin1String; +} + QDebug operator<<(QDebug dbg, TestObject& testObject) { QDebugStateSaver saver(dbg); diff --git a/sources/pyside6/tests/pysidetest/testobject.h b/sources/pyside6/tests/pysidetest/testobject.h index d3f0b201..a095a382 100644 --- a/sources/pyside6/tests/pysidetest/testobject.h +++ b/sources/pyside6/tests/pysidetest/testobject.h @@ -8,12 +8,15 @@ #include +#include #include #include #include QT_FORWARD_DECLARE_CLASS(QDebug) +using IntList = QList; + class IntValue { public: @@ -44,6 +47,14 @@ public: void emitSignalWithDefaultValue_bool(); void emitSignalWithTypedefValue(int value); + void emitSignalWithContainerTypedefValue(const IntList &il); + + void emitFlagsSignal(Qt::Alignment alignment); + + static constexpr auto LATIN1_TEST_FIELD = QLatin1StringView("test"); + + void setQLatin1String(QLatin1String v); + QString qLatin1String() const; signals: void idValue(int newValue); @@ -52,10 +63,13 @@ signals: void childrenChanged(const QList&); void signalWithDefaultValue(bool value = false); void signalWithTypedefValue(TypedefValue value); + void signalWithContainerTypedefValue(const IntList &il); + void flagsSignal(Qt::Alignment alignment); private: int m_idValue; QList m_children; + QString m_qLatin1String; }; PYSIDETEST_API QDebug operator<<(QDebug dbg, TestObject &testObject); diff --git a/sources/pyside6/tests/pysidetest/testqvariantenum.h b/sources/pyside6/tests/pysidetest/testqvariantenum.h index 46564d26..4b729e3d 100644 --- a/sources/pyside6/tests/pysidetest/testqvariantenum.h +++ b/sources/pyside6/tests/pysidetest/testqvariantenum.h @@ -22,4 +22,14 @@ private: QVariant m_enum; }; +class PYSIDETEST_API QVariantHolder // modeled after Q3DParameter, test QVariant conversion +{ +public: + void setValue(QVariant v) { m_variant = v; } + QVariant value() const { return m_variant; } + +private: + QVariant m_variant; +}; + #endif // TESTQVARIANT_H diff --git a/sources/pyside6/tests/pysidetest/typedef_signal_test.py b/sources/pyside6/tests/pysidetest/typedef_signal_test.py index dfe5311e..d0bdc880 100644 --- a/sources/pyside6/tests/pysidetest/typedef_signal_test.py +++ b/sources/pyside6/tests/pysidetest/typedef_signal_test.py @@ -10,7 +10,7 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from init_paths import init_test_paths init_test_paths(True) -from PySide6.QtCore import QObject +from PySide6.QtCore import QObject, Slot from testbinding import TestObject @@ -23,6 +23,10 @@ class Receiver(QObject): def slot(self, value): self.received = value + @Slot("IntList") + def containerSlot(self, value): + self.received = value + class TypedefSignal(unittest.TestCase): @@ -34,6 +38,15 @@ class TypedefSignal(unittest.TestCase): obj.emitSignalWithTypedefValue(2) self.assertEqual(receiver.received.value, 2) + def testContainerTypedef(self): + obj = TestObject(0) + receiver = Receiver() + + test_list = [1, 2] + obj.signalWithContainerTypedefValue.connect(receiver.containerSlot) + obj.emitSignalWithContainerTypedefValue(test_list) + self.assertEqual(receiver.received, test_list) + if __name__ == '__main__': unittest.main() diff --git a/sources/pyside6/tests/pysidetest/typesystem_pysidetest.xml b/sources/pyside6/tests/pysidetest/typesystem_pysidetest.xml index bd959f7e..592d90a8 100644 --- a/sources/pyside6/tests/pysidetest/typesystem_pysidetest.xml +++ b/sources/pyside6/tests/pysidetest/typesystem_pysidetest.xml @@ -72,6 +72,8 @@ + + diff --git a/sources/pyside6/tests/registry/util.py b/sources/pyside6/tests/registry/util.py index a1c5e05e..5d81926a 100644 --- a/sources/pyside6/tests/registry/util.py +++ b/sources/pyside6/tests/registry/util.py @@ -71,14 +71,8 @@ def linux_distribution(): # distro package, ASAP! The distro has been extracted from Python, # because it changes more often than the Python version. distribution = [] - try: - import distro - distribution = distro.linux_distribution() - except ImportError: - # platform.linux_distribution() was removed in 3.8 - if sys.version_info[:2] < (3, 8): - import platform - distribution = platform.linux_distribution() + import distro + distribution = distro.linux_distribution() if distribution: return "".join(distribution[:2]).lower() warnings.warn('Cannot determine Linux distribution, please install distro', diff --git a/sources/pyside6/tests/signals/CMakeLists.txt b/sources/pyside6/tests/signals/CMakeLists.txt index 96946c00..ff342adc 100644 --- a/sources/pyside6/tests/signals/CMakeLists.txt +++ b/sources/pyside6/tests/signals/CMakeLists.txt @@ -16,6 +16,7 @@ PYSIDE_TEST(leaking_signal_test.py) PYSIDE_TEST(multiple_connections_gui_test.py) PYSIDE_TEST(multiple_connections_test.py) PYSIDE_TEST(pysignal_test.py) +PYSIDE_TEST(qobject_callable_connect_test.py) PYSIDE_TEST(qobject_destroyed_test.py) PYSIDE_TEST(qobject_receivers_test.py) PYSIDE_TEST(qobject_sender_test.py) diff --git a/sources/pyside6/tests/signals/args_dont_match_test.py b/sources/pyside6/tests/signals/args_dont_match_test.py index e200cedb..4f56be34 100644 --- a/sources/pyside6/tests/signals/args_dont_match_test.py +++ b/sources/pyside6/tests/signals/args_dont_match_test.py @@ -11,7 +11,11 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from init_paths import init_test_paths init_test_paths(False) -from PySide6.QtCore import QObject, SIGNAL +from PySide6.QtCore import QObject, Signal + + +class Sender(QObject): + the_signal = Signal(int, int, int) class ArgsDontMatch(unittest.TestCase): @@ -21,9 +25,9 @@ class ArgsDontMatch(unittest.TestCase): def testConnectSignalToSlotWithLessArgs(self): self.ok = False - obj1 = QObject() - QObject.connect(obj1, SIGNAL('the_signal(int, int, int)'), self.callback) - obj1.emit(SIGNAL('the_signal(int, int, int)'), 1, 2, 3) + obj1 = Sender() + obj1.the_signal.connect(self.callback) + obj1.the_signal.emit(1, 2, 3) self.assertTrue(self.ok) diff --git a/sources/pyside6/tests/signals/bug_311.py b/sources/pyside6/tests/signals/bug_311.py index f2bbbc09..e2747617 100644 --- a/sources/pyside6/tests/signals/bug_311.py +++ b/sources/pyside6/tests/signals/bug_311.py @@ -52,4 +52,3 @@ class SignaltoSignalTest(UsesQApplication): if __name__ == '__main__': unittest.main() - diff --git a/sources/pyside6/tests/signals/bug_312.py b/sources/pyside6/tests/signals/bug_312.py index de1a2f6a..80d56a02 100644 --- a/sources/pyside6/tests/signals/bug_312.py +++ b/sources/pyside6/tests/signals/bug_312.py @@ -11,44 +11,29 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from init_paths import init_test_paths init_test_paths(False) -from PySide6.QtCore import QObject, SIGNAL +from PySide6.QtCore import QObject, Signal MAX_LOOPS = 5 MAX_OBJECTS = 200 -class Dummy(object): - def __init__(self, parent): - self._parent = parent - - def callback(self): - self._called = True +class Sender(QObject): + fire = Signal() class MultipleSlots(unittest.TestCase): def myCB(self): self._count += 1 - """ - def testUnboundSignal(self): - o = QObject() - self._count = 0 - for i in range(MAX_OBJECTS): - QObject.connect(o, SIGNAL("fire()"), lambda: self.myCB()) - - o.emit(SIGNAL("fire()")) - self.assertEqual(self._count, MAX_OBJECTS) - - """ def testDisconnectCleanup(self): for c in range(MAX_LOOPS): self._count = 0 self._senders = [] for i in range(MAX_OBJECTS): - o = QObject() - QObject.connect(o, SIGNAL("fire()"), lambda: self.myCB()) + o = Sender() + o.fire.connect(lambda: self.myCB()) self._senders.append(o) - o.emit(SIGNAL("fire()")) + o.fire.emit() self.assertEqual(self._count, MAX_OBJECTS) @@ -58,5 +43,3 @@ class MultipleSlots(unittest.TestCase): if __name__ == '__main__': unittest.main() - - diff --git a/sources/pyside6/tests/signals/bug_319.py b/sources/pyside6/tests/signals/bug_319.py index bae0d8fd..657733af 100644 --- a/sources/pyside6/tests/signals/bug_319.py +++ b/sources/pyside6/tests/signals/bug_319.py @@ -45,4 +45,3 @@ class SignaltoSignalTest(UsesQApplication): if __name__ == '__main__': unittest.main() - diff --git a/sources/pyside6/tests/signals/bug_79.py b/sources/pyside6/tests/signals/bug_79.py index 0e12301f..77ac621d 100644 --- a/sources/pyside6/tests/signals/bug_79.py +++ b/sources/pyside6/tests/signals/bug_79.py @@ -30,7 +30,7 @@ class ConnectTest(unittest.TestCase): def testNoLeaks_ConnectAndDisconnect(self): self._called = None - app = QApplication([]) + app = QApplication([]) # noqa: F841 o = QTreeView() o.setModel(QStandardItemModel()) o.selectionModel().destroyed.connect(self.callback) diff --git a/sources/pyside6/tests/signals/decorators_test.py b/sources/pyside6/tests/signals/decorators_test.py index 5b3b5469..b29339ee 100644 --- a/sources/pyside6/tests/signals/decorators_test.py +++ b/sources/pyside6/tests/signals/decorators_test.py @@ -11,7 +11,11 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from init_paths import init_test_paths init_test_paths(False) -from PySide6.QtCore import QObject, Slot, SIGNAL, SLOT +from PySide6.QtCore import QObject, Slot, Signal + + +class Sender(QObject): + mySignal = Signal() class MyObject(QObject): @@ -57,9 +61,10 @@ class StaticMetaObjectTest(unittest.TestCase): self.assertTrue(m.indexOfSlot('mySlot4(QString,int)') > 0) def testEmission(self): + sender = Sender() o = MyObject() - o.connect(SIGNAL("mySignal()"), o, SLOT("mySlot()")) - o.emit(SIGNAL("mySignal()")) + sender.mySignal.connect(o.mySlot) + sender.mySignal.emit() self.assertTrue(o._slotCalledCount == 1) def testResult(self): diff --git a/sources/pyside6/tests/signals/invalid_callback_test.py b/sources/pyside6/tests/signals/invalid_callback_test.py index b5252870..2788c1d1 100644 --- a/sources/pyside6/tests/signals/invalid_callback_test.py +++ b/sources/pyside6/tests/signals/invalid_callback_test.py @@ -13,7 +13,7 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from init_paths import init_test_paths init_test_paths(False) -from PySide6.QtCore import QObject, SIGNAL +from PySide6.QtCore import QObject class InvalidCallback(unittest.TestCase): @@ -34,10 +34,8 @@ class InvalidCallback(unittest.TestCase): def testIntegerCb(self): # Test passing an int as callback to QObject.connect - self.assertRaises(TypeError, QObject.connect, self.obj, - SIGNAL('destroyed()'), 42) + self.assertRaises(TypeError, self.obj.destroyed.connect, 42) if __name__ == '__main__': unittest.main() - diff --git a/sources/pyside6/tests/signals/lambda_gui_test.py b/sources/pyside6/tests/signals/lambda_gui_test.py index 45a6d6ac..2123e720 100644 --- a/sources/pyside6/tests/signals/lambda_gui_test.py +++ b/sources/pyside6/tests/signals/lambda_gui_test.py @@ -12,43 +12,39 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from init_paths import init_test_paths init_test_paths(False) -from PySide6.QtCore import QObject, SIGNAL - -try: - from PySide6.QtWidgets import QSpinBox, QPushButton - hasQtGui = True -except ImportError: - hasQtGui = False +from PySide6.QtWidgets import QSpinBox, QPushButton from helper.usesqapplication import UsesQApplication -if hasQtGui: - class Control: - def __init__(self): - self.arg = False - - class QtGuiSigLambda(UsesQApplication): - - def testButton(self): - # Connecting a lambda to a QPushButton.clicked() - obj = QPushButton('label') - ctr = Control() - func = lambda: setattr(ctr, 'arg', True) - obj.clicked.connect(func) - obj.click() - self.assertTrue(ctr.arg) - QObject.disconnect(obj, SIGNAL('clicked()'), func) - - def testSpinButton(self): - # Connecting a lambda to a QPushButton.clicked() - obj = QSpinBox() - ctr = Control() - arg = 444 - func = lambda x: setattr(ctr, 'arg', 444) - obj.valueChanged.connect(func) - obj.setValue(444) - self.assertEqual(ctr.arg, arg) - QObject.disconnect(obj, SIGNAL('valueChanged(int)'), func) + +class Control: + def __init__(self): + self.arg = False + + +class QtWidgetsSigLambda(UsesQApplication): + + def testButton(self): + # Connecting a lambda to a QPushButton.clicked() + obj = QPushButton('label') + ctr = Control() + func = lambda: setattr(ctr, 'arg', True) # noqa: E731 + obj.clicked.connect(func) + obj.click() + self.assertTrue(ctr.arg) + self.assertTrue(obj.clicked.disconnect(func)) + + def testSpinButton(self): + # Connecting a lambda to a QPushButton.clicked() + obj = QSpinBox() + ctr = Control() + arg = 444 + func = lambda x: setattr(ctr, 'arg', 444) # noqa: E731 + obj.valueChanged.connect(func) + obj.setValue(444) + self.assertEqual(ctr.arg, arg) + self.assertTrue(obj.valueChanged.disconnect(func)) + if __name__ == '__main__': unittest.main() diff --git a/sources/pyside6/tests/signals/lambda_test.py b/sources/pyside6/tests/signals/lambda_test.py index a91a9795..23fcdf5f 100644 --- a/sources/pyside6/tests/signals/lambda_test.py +++ b/sources/pyside6/tests/signals/lambda_test.py @@ -7,18 +7,34 @@ import os import sys import unittest +import weakref from pathlib import Path sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from init_paths import init_test_paths init_test_paths(False) -from PySide6.QtCore import QObject, SIGNAL, QProcess +from PySide6.QtCore import QCoreApplication, QObject, Signal, SIGNAL, QProcess from helper.usesqapplication import UsesQApplication -class Dummy(QObject): +class Sender(QObject): + void_signal = Signal() + int_signal = Signal(int) + + def __init__(self, parent=None): + super().__init__(parent) + self._delayed_int = 0 + + def emit_void(self): + self.void_signal.emit() + + def emit_int(self, v): + self.int_signal.emit(v) + + +class Receiver(QObject): def __init__(self, *args): super().__init__(*args) @@ -28,45 +44,79 @@ class BasicCase(unittest.TestCase): def testSimplePythonSignalNoArgs(self): # Connecting a lambda to a simple python signal without arguments - obj = Dummy() - QObject.connect(obj, SIGNAL('foo()'), - lambda: setattr(obj, 'called', True)) - obj.emit(SIGNAL('foo()')) - self.assertTrue(obj.called) + receiver = Receiver() + sender = Sender() + sender.void_signal.connect(lambda: setattr(receiver, 'called', True)) + sender.emit_void() + self.assertTrue(receiver.called) def testSimplePythonSignal(self): # Connecting a lambda to a simple python signal witharguments - obj = Dummy() + receiver = Receiver() + sender = Sender() + arg = 42 + sender.int_signal.connect(lambda x: setattr(receiver, 'arg', arg)) + sender.emit_int(arg) + self.assertEqual(receiver.arg, arg) + + def testSimplePythonSignalNoArgsString(self): + # Connecting a lambda to a simple python signal without arguments + receiver = Receiver() + sender = Sender() + QObject.connect(sender, SIGNAL('void_signal()'), + lambda: setattr(receiver, 'called', True)) + sender.emit_void() + self.assertTrue(receiver.called) + + def testSimplePythonSignalString(self): + # Connecting a lambda to a simple python signal witharguments + receiver = Receiver() + sender = Sender() arg = 42 - QObject.connect(obj, SIGNAL('foo(int)'), - lambda x: setattr(obj, 'arg', 42)) - obj.emit(SIGNAL('foo(int)'), arg) - self.assertEqual(obj.arg, arg) + QObject.connect(sender, SIGNAL('int_signal(int)'), + lambda x: setattr(receiver, 'arg', arg)) + sender.emit_int(arg) + self.assertEqual(receiver.arg, arg) class QtSigLambda(UsesQApplication): qapplication = True - def testNoArgs(self): - '''Connecting a lambda to a signal without arguments''' - proc = QProcess() - dummy = Dummy() - QObject.connect(proc, SIGNAL('started()'), - lambda: setattr(dummy, 'called', True)) - proc.start(sys.executable, ['-c', '""']) - proc.waitForFinished() - self.assertTrue(dummy.called) - def testWithArgs(self): - '''Connecting a lambda to a signal with arguments''' + '''Connecting a lambda to a signal with and without arguments''' proc = QProcess() - dummy = Dummy() - QObject.connect(proc, SIGNAL('finished(int)'), - lambda x: setattr(dummy, 'called', x)) + dummy = Receiver() + proc.started.connect(lambda: setattr(dummy, 'called', True)) + proc.finished.connect(lambda x: setattr(dummy, 'exit_code', x)) + proc.start(sys.executable, ['-c', '""']) - proc.waitForFinished() - self.assertEqual(dummy.called, proc.exitCode()) + self.assertTrue(proc.waitForStarted()) + self.assertTrue(proc.waitForFinished()) + + self.assertTrue(dummy.called) + self.assertEqual(dummy.exit_code, proc.exitCode()) + + def testRelease(self): + """PYSIDE-2646: Test whether main thread target slot lambda/methods + (and their captured objects) are released by the signal manager + after a while.""" + + def do_connect(sender): + receiver = Receiver() + sender.void_signal.connect(lambda: setattr(receiver, 'called', True)) + return receiver + + sender = Sender() + receiver = weakref.ref(do_connect(sender)) + sender.emit_void() + self.assertTrue(receiver().called) + del sender + for i in range(3): + if not receiver(): + break + QCoreApplication.processEvents() + self.assertFalse(receiver()) if __name__ == '__main__': diff --git a/sources/pyside6/tests/signals/leaking_signal_test.py b/sources/pyside6/tests/signals/leaking_signal_test.py index e7aff962..666ae7a1 100644 --- a/sources/pyside6/tests/signals/leaking_signal_test.py +++ b/sources/pyside6/tests/signals/leaking_signal_test.py @@ -4,7 +4,6 @@ import os import sys import unittest -import weakref from pathlib import Path sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) @@ -21,7 +20,7 @@ class LeakingSignal(unittest.TestCase): class Emitter(QObject): my_signal = Signal(object) - emitter = Emitter() + emitter = Emitter() # noqa: F841 if __name__ == '__main__': diff --git a/sources/pyside6/tests/signals/multiple_connections_gui_test.py b/sources/pyside6/tests/signals/multiple_connections_gui_test.py index 09781175..295369b7 100644 --- a/sources/pyside6/tests/signals/multiple_connections_gui_test.py +++ b/sources/pyside6/tests/signals/multiple_connections_gui_test.py @@ -1,9 +1,7 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -from functools import partial import os -import random import sys import unittest @@ -12,22 +10,16 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from init_paths import init_test_paths init_test_paths(False) -from PySide6.QtCore import QObject, SIGNAL - -try: - from PySide6.QtWidgets import QPushButton, QSpinBox - hasQtGui = True -except ImportError: - hasQtGui = False +from PySide6.QtWidgets import QPushButton, QSpinBox from helper.basicpyslotcase import BasicPySlotCase from helper.usesqapplication import UsesQApplication -class MultipleSignalConnections(unittest.TestCase): - '''Base class for multiple signal connection testing''' +class QtGuiMultipleSlots(UsesQApplication): + '''Multiple connections to QtGui signals''' - def run_many(self, sender, signal, emitter, receivers, args=None): + def run_many(self, signal, emitter, receivers, args=None): """Utility method to connect a list of receivers to a signal. sender - QObject that will emit the signal signal - string with the signal signature @@ -41,7 +33,7 @@ class MultipleSignalConnections(unittest.TestCase): for rec in receivers: rec.setUp() - QObject.connect(sender, SIGNAL(signal), rec.cb) + signal.connect(rec.cb) rec.args = tuple(args) emitter(*args) @@ -49,24 +41,20 @@ class MultipleSignalConnections(unittest.TestCase): for rec in receivers: self.assertTrue(rec.called) + def testButtonClick(self): + """Multiple connections to QPushButton.clicked()""" + sender = QPushButton('button') + receivers = [BasicPySlotCase() for x in range(30)] + self.run_many(sender.clicked, sender.click, receivers) -if hasQtGui: - class QtGuiMultipleSlots(UsesQApplication, MultipleSignalConnections): - '''Multiple connections to QtGui signals''' - - def testButtonClick(self): - """Multiple connections to QPushButton.clicked()""" - sender = QPushButton('button') - receivers = [BasicPySlotCase() for x in range(30)] - self.run_many(sender, 'clicked()', sender.click, receivers) + def testSpinBoxValueChanged(self): + """Multiple connections to QSpinBox.valueChanged(int)""" + sender = QSpinBox() + # FIXME if number of receivers if higher than 50, segfaults + receivers = [BasicPySlotCase() for x in range(10)] + self.run_many(sender.valueChanged, sender.setValue, + receivers, (1,)) - def testSpinBoxValueChanged(self): - """Multiple connections to QSpinBox.valueChanged(int)""" - sender = QSpinBox() - # FIXME if number of receivers if higher than 50, segfaults - receivers = [BasicPySlotCase() for x in range(10)] - self.run_many(sender, 'valueChanged(int)', sender.setValue, - receivers, (1,)) if __name__ == '__main__': unittest.main() diff --git a/sources/pyside6/tests/signals/multiple_connections_test.py b/sources/pyside6/tests/signals/multiple_connections_test.py index b6aa33f2..23385179 100644 --- a/sources/pyside6/tests/signals/multiple_connections_test.py +++ b/sources/pyside6/tests/signals/multiple_connections_test.py @@ -11,7 +11,7 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from init_paths import init_test_paths init_test_paths(False) -from PySide6.QtCore import QObject, SIGNAL, QProcess +from PySide6.QtCore import QObject, Signal, QProcess from helper.basicpyslotcase import BasicPySlotCase from helper.usesqapplication import UsesQApplication @@ -20,7 +20,7 @@ from helper.usesqapplication import UsesQApplication class MultipleSignalConnections(unittest.TestCase): '''Base class for multiple signal connection testing''' - def run_many(self, sender, signal, emitter, receivers, args=None): + def run_many(self, signal, emitter, receivers, args=None): """Utility method to connect a list of receivers to a signal. sender - QObject that will emit the signal signal - string with the signal signature @@ -33,7 +33,7 @@ class MultipleSignalConnections(unittest.TestCase): args = tuple() for rec in receivers: rec.setUp() - self.assertTrue(QObject.connect(sender, SIGNAL(signal), rec.cb)) + self.assertTrue(signal.connect(rec.cb)) rec.args = tuple(args) emitter(*args) @@ -48,13 +48,14 @@ class PythonMultipleSlots(UsesQApplication, MultipleSignalConnections): def testPythonSignal(self): """Multiple connections to a python signal (short-circuit)""" - class Dummy(QObject): - pass + class Sender(QObject): - sender = Dummy() + foobar = Signal(int) + + sender = Sender() receivers = [BasicPySlotCase() for x in range(10)] - self.run_many(sender, 'foobar(int)', partial(sender.emit, - SIGNAL('foobar(int)')), receivers, (0, )) + self.run_many(sender.foobar, partial(sender.foobar.emit), + receivers, (0, )) class QProcessMultipleSlots(UsesQApplication, MultipleSignalConnections): @@ -67,9 +68,10 @@ class QProcessMultipleSlots(UsesQApplication, MultipleSignalConnections): def start_proc(*args): sender.start(sys.executable, ['-c', '""']) - sender.waitForFinished() + self.assertTrue(sender.waitForStarted()) + self.assertTrue(sender.waitForFinished()) - self.run_many(sender, 'started()', start_proc, receivers) + self.run_many(sender.started, start_proc, receivers) def testQProcessFinished(self): '''Multiple connections to QProcess.finished(int)''' @@ -78,9 +80,10 @@ class QProcessMultipleSlots(UsesQApplication, MultipleSignalConnections): def start_proc(*args): sender.start(sys.executable, ['-c', '""']) - sender.waitForFinished() + self.assertTrue(sender.waitForStarted()) + self.assertTrue(sender.waitForFinished()) - self.run_many(sender, 'finished(int)', start_proc, receivers, (0,)) + self.run_many(sender.finished, start_proc, receivers, (0, QProcess.ExitStatus.NormalExit)) if __name__ == '__main__': diff --git a/sources/pyside6/tests/signals/pysignal_test.py b/sources/pyside6/tests/signals/pysignal_test.py index e3d6a55d..d6f44edf 100644 --- a/sources/pyside6/tests/signals/pysignal_test.py +++ b/sources/pyside6/tests/signals/pysignal_test.py @@ -11,31 +11,31 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from init_paths import init_test_paths init_test_paths(False) -from PySide6.QtCore import QObject, SIGNAL, SLOT, Qt - -try: - from PySide6.QtWidgets import QSpinBox, QApplication, QWidget - hasQtGui = True -except ImportError: - hasQtGui = False +from PySide6.QtCore import QObject, Signal, Qt +from PySide6.QtWidgets import QSpinBox, QApplication, QWidget # noqa: F401 from helper.usesqapplication import UsesQApplication -class Dummy(QObject): - """Dummy class used in this test.""" +TEST_LIST = ["item1", "item2", "item3"] + + +class Sender(QObject): + """Sender class used in this test.""" + + foo = Signal() + foo_int = Signal(int) + dummy = Signal(str) + dummy2 = Signal(str, list) + def __init__(self, parent=None): - QObject.__init__(self, parent) + super().__init__(parent) def callDummy(self): - self.emit(SIGNAL("dummy(PyObject)"), "PyObject") + self.dummy.emit("PyObject") def callDummy2(self): - lst = [] - lst.append("item1") - lst.append("item2") - lst.append("item3") - self.emit(SIGNAL("dummy2(PyObject, PyObject)"), "PyObject0", lst) + self.dummy2.emit("PyObject0", TEST_LIST) class PyObjectType(UsesQApplication): @@ -46,35 +46,33 @@ class PyObjectType(UsesQApplication): def mySlot2(self, arg0, arg1): self.assertEqual(arg0, "PyObject0") - self.assertEqual(arg1[0], "item1") - self.assertEqual(arg1[1], "item2") - self.assertEqual(arg1[2], "item3") + self.assertEqual(arg1, TEST_LIST) self.callCount += 1 if self.running: self.app.quit() def setUp(self): - super(PyObjectType, self).setUp() + super().setUp() self.callCount = 0 self.running = False def testWithOneArg(self): - o = Dummy() - o.connect(SIGNAL("dummy(PyObject)"), self.mySlot) + o = Sender() + o.dummy.connect(self.mySlot) o.callDummy() self.assertEqual(self.callCount, 1) def testWithTwoArg(self): - o = Dummy() - o.connect(SIGNAL("dummy2(PyObject,PyObject)"), self.mySlot2) + o = Sender() + o.dummy2.connect(self.mySlot2) o.callDummy2() self.assertEqual(self.callCount, 1) def testAsyncSignal(self): self.called = False self.running = True - o = Dummy() - o.connect(SIGNAL("dummy2(PyObject,PyObject)"), self.mySlot2, Qt.QueuedConnection) + o = Sender() + o.dummy2.connect(self.mySlot2, Qt.QueuedConnection) o.callDummy2() self.app.exec() self.assertEqual(self.callCount, 1) @@ -82,8 +80,8 @@ class PyObjectType(UsesQApplication): def testTwice(self): self.called = False self.running = True - o = Dummy() - o.connect(SIGNAL("dummy2(PyObject,PyObject)"), self.mySlot2, Qt.QueuedConnection) + o = Sender() + o.dummy2.connect(self.mySlot2, Qt.QueuedConnection) o.callDummy2() o.callDummy2() self.app.exec() @@ -97,7 +95,7 @@ class PythonSigSlot(unittest.TestCase): def tearDown(self): try: del self.args - except: + except: # noqa: E722 pass # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion gc.collect() @@ -108,98 +106,98 @@ class PythonSigSlot(unittest.TestCase): def testNoArgs(self): """Python signal and slots without arguments""" - obj1 = Dummy() + obj1 = Sender() - QObject.connect(obj1, SIGNAL('foo()'), self.callback) + obj1.foo.connect(self.callback) self.args = tuple() - obj1.emit(SIGNAL('foo()'), *self.args) + obj1.foo.emit(*self.args) self.assertTrue(self.called) def testWithArgs(self): """Python signal and slots with integer arguments""" - obj1 = Dummy() + obj1 = Sender() - QObject.connect(obj1, SIGNAL('foo(int)'), self.callback) + obj1.foo_int.connect(self.callback) self.args = (42,) - obj1.emit(SIGNAL('foo(int)'), *self.args) + obj1.foo_int.emit(*self.args) self.assertTrue(self.called) def testDisconnect(self): - obj1 = Dummy() + obj1 = Sender() - QObject.connect(obj1, SIGNAL('foo(int)'), self.callback) - QObject.disconnect(obj1, SIGNAL('foo(int)'), self.callback) + obj1.foo_int.connect(self.callback) + self.assertTrue(obj1.foo_int.disconnect(self.callback)) self.args = (42, ) - obj1.emit(SIGNAL('foo(int)'), *self.args) + obj1.foo_int.emit(*self.args) self.assertTrue(not self.called) -if hasQtGui: - class SpinBoxPySignal(UsesQApplication): - """Tests the connection of python signals to QSpinBox qt slots.""" +class SpinBoxPySignal(UsesQApplication): + """Tests the connection of python signals to QSpinBox qt slots.""" - def setUp(self): - super(SpinBoxPySignal, self).setUp() - self.obj = Dummy() - self.spin = QSpinBox() - self.spin.setValue(0) + def setUp(self): + super().setUp() + self.obj = Sender() + self.spin = QSpinBox() + self.spin.setValue(0) - def tearDown(self): - super(SpinBoxPySignal, self).tearDown() - del self.obj - del self.spin - # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion - gc.collect() + def tearDown(self): + super().tearDown() + del self.obj + del self.spin + # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion + gc.collect() + + def testValueChanged(self): + """Emission of a python signal to QSpinBox setValue(int)""" - def testValueChanged(self): - """Emission of a python signal to QSpinBox setValue(int)""" - QObject.connect(self.obj, SIGNAL('dummy(int)'), self.spin, SLOT('setValue(int)')) - self.assertEqual(self.spin.value(), 0) + self.obj.foo_int.connect(self.spin.setValue) + self.assertEqual(self.spin.value(), 0) - self.obj.emit(SIGNAL('dummy(int)'), 4) - self.assertEqual(self.spin.value(), 4) + self.obj.foo_int.emit(4) + self.assertEqual(self.spin.value(), 4) - def testValueChangedMultiple(self): - """Multiple emissions of a python signal to QSpinBox setValue(int)""" - QObject.connect(self.obj, SIGNAL('dummy(int)'), self.spin, SLOT('setValue(int)')) - self.assertEqual(self.spin.value(), 0) + def testValueChangedMultiple(self): + """Multiple emissions of a python signal to QSpinBox setValue(int)""" + self.obj.foo_int.connect(self.spin.setValue) + self.assertEqual(self.spin.value(), 0) - self.obj.emit(SIGNAL('dummy(int)'), 4) - self.assertEqual(self.spin.value(), 4) + self.obj.foo_int.emit(4) + self.assertEqual(self.spin.value(), 4) - self.obj.emit(SIGNAL('dummy(int)'), 77) - self.assertEqual(self.spin.value(), 77) + self.obj.foo_int.emit(77) + self.assertEqual(self.spin.value(), 77) -if hasQtGui: - class WidgetPySignal(UsesQApplication): - """Tests the connection of python signals to QWidget qt slots.""" +class WidgetPySignal(UsesQApplication): + """Tests the connection of python signals to QWidget qt slots.""" + + def setUp(self): + super(WidgetPySignal, self).setUp() + self.obj = Sender() + self.widget = QWidget() - def setUp(self): - super(WidgetPySignal, self).setUp() - self.obj = Dummy() - self.widget = QWidget() + def tearDown(self): + super(WidgetPySignal, self).tearDown() + del self.obj + del self.widget + # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion + gc.collect() - def tearDown(self): - super(WidgetPySignal, self).tearDown() - del self.obj - del self.widget - # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion - gc.collect() + def testShow(self): + """Emission of a python signal to QWidget slot show()""" + self.widget.hide() - def testShow(self): - """Emission of a python signal to QWidget slot show()""" - self.widget.hide() + self.obj.foo.connect(self.widget.show) + self.assertTrue(not self.widget.isVisible()) - QObject.connect(self.obj, SIGNAL('dummy()'), self.widget, SLOT('show()')) - self.assertTrue(not self.widget.isVisible()) + self.obj.foo.emit() + self.assertTrue(self.widget.isVisible()) - self.obj.emit(SIGNAL('dummy()')) - self.assertTrue(self.widget.isVisible()) if __name__ == '__main__': unittest.main() diff --git a/sources/pyside6/tests/signals/qobject_callable_connect_test.py b/sources/pyside6/tests/signals/qobject_callable_connect_test.py new file mode 100644 index 00000000..a7a26d6f --- /dev/null +++ b/sources/pyside6/tests/signals/qobject_callable_connect_test.py @@ -0,0 +1,45 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import os +import sys +import unittest + +from pathlib import Path +sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) +from init_paths import init_test_paths +init_test_paths(False) + +from PySide6.QtCore import QObject, Signal + + +class Emitter(QObject): + sig = Signal(int) + + +class CallableObject(QObject): + called = False + x = 0 + + def __call__(self, x: int): + self.called = True + self.x = x + + +class QObjectCallableConnectTest(unittest.TestCase): + '''Test case for QObject.connect() when the callable is also a QObject.''' + + def testCallableConnect(self): + emitter = Emitter() + obj = CallableObject() + x = 1 + + emitter.sig.connect(obj) + emitter.sig.emit(x) + + self.assertTrue(obj.called) + self.assertEqual(obj.x, x) + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/signals/qobject_destroyed_test.py b/sources/pyside6/tests/signals/qobject_destroyed_test.py index 08807e78..a21762b4 100644 --- a/sources/pyside6/tests/signals/qobject_destroyed_test.py +++ b/sources/pyside6/tests/signals/qobject_destroyed_test.py @@ -11,7 +11,7 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from init_paths import init_test_paths init_test_paths(False) -from PySide6.QtCore import QObject, SIGNAL +from PySide6.QtCore import QObject class QObjectDestroyed(unittest.TestCase): diff --git a/sources/pyside6/tests/signals/qobject_sender_test.py b/sources/pyside6/tests/signals/qobject_sender_test.py index 485584ec..9c1121eb 100644 --- a/sources/pyside6/tests/signals/qobject_sender_test.py +++ b/sources/pyside6/tests/signals/qobject_sender_test.py @@ -13,7 +13,7 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from init_paths import init_test_paths init_test_paths(False) -from PySide6.QtCore import QCoreApplication, QObject, QTimer, SIGNAL +from PySide6.QtCore import QCoreApplication, QObject, QTimer, Signal from helper.usesqapplication import UsesQApplication @@ -22,6 +22,10 @@ class ExtQTimer(QTimer): super().__init__() +class Sender(QObject): + foo = Signal() + + class Receiver(QObject): def __init__(self): super().__init__() @@ -37,10 +41,10 @@ class ObjectSenderTest(unittest.TestCase): '''Test case for QObject.sender() method.''' def testSenderPythonSignal(self): - sender = QObject() + sender = Sender() recv = Receiver() - QObject.connect(sender, SIGNAL('foo()'), recv.callback) - sender.emit(SIGNAL('foo()')) + sender.foo.connect(recv.callback) + sender.foo.emit() self.assertEqual(sender, recv.the_sender) @@ -48,10 +52,10 @@ class ObjectSenderCheckOnReceiverTest(unittest.TestCase): '''Test case for QObject.sender() method, this one tests the equality on the Receiver object.''' def testSenderPythonSignal(self): - sender = QObject() + sender = Sender() recv = Receiver() - QObject.connect(sender, SIGNAL('foo()'), recv.callback) - sender.emit(SIGNAL('foo()')) + sender.foo.connect(recv.callback) + sender.foo.emit() self.assertEqual(sender, recv.the_sender) diff --git a/sources/pyside6/tests/signals/ref01_test.py b/sources/pyside6/tests/signals/ref01_test.py index a830b55d..1a62b221 100644 --- a/sources/pyside6/tests/signals/ref01_test.py +++ b/sources/pyside6/tests/signals/ref01_test.py @@ -37,5 +37,3 @@ class BoundAndUnboundSignalsTest(unittest.TestCase): if __name__ == '__main__': unittest.main() - - diff --git a/sources/pyside6/tests/signals/ref02_test.py b/sources/pyside6/tests/signals/ref02_test.py index e0d71cff..54b6f4a5 100644 --- a/sources/pyside6/tests/signals/ref02_test.py +++ b/sources/pyside6/tests/signals/ref02_test.py @@ -12,7 +12,7 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from init_paths import init_test_paths init_test_paths(False) -from PySide6.QtCore import QCoreApplication, QTimeLine +from PySide6.QtCore import QTimeLine from helper.usesqapplication import UsesQApplication @@ -58,4 +58,3 @@ class NativeSignalsTest(UsesQApplication): if __name__ == '__main__': unittest.main() - diff --git a/sources/pyside6/tests/signals/ref03_test.py b/sources/pyside6/tests/signals/ref03_test.py index 2df2d7ce..c43c2e54 100644 --- a/sources/pyside6/tests/signals/ref03_test.py +++ b/sources/pyside6/tests/signals/ref03_test.py @@ -40,4 +40,3 @@ class DisconnectSignalsTest(unittest.TestCase): if __name__ == '__main__': unittest.main() - diff --git a/sources/pyside6/tests/signals/ref04_test.py b/sources/pyside6/tests/signals/ref04_test.py index b478aff2..fce80145 100644 --- a/sources/pyside6/tests/signals/ref04_test.py +++ b/sources/pyside6/tests/signals/ref04_test.py @@ -53,17 +53,6 @@ class UserSignalTest(unittest.TestCase): self.emitter.mySignal.emit() self.assertEqual(self.counter, 2) -# def testConnectWithConfigureMethod(self): -# -# def slot(): -# self.counter += 1 -# -# self.emitter.pyqtConfigure(mySignal=slot) -# self.assertEqual(self.counter, 0) -# self.emitter.mySignal.emit() -# self.assertEqual(self.counter, 1) - if __name__ == '__main__': unittest.main() - diff --git a/sources/pyside6/tests/signals/ref05_test.py b/sources/pyside6/tests/signals/ref05_test.py index b6c5b4a1..fb9debf3 100644 --- a/sources/pyside6/tests/signals/ref05_test.py +++ b/sources/pyside6/tests/signals/ref05_test.py @@ -12,7 +12,7 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from init_paths import init_test_paths init_test_paths(False) -from PySide6.QtCore import QObject, QCoreApplication, QTimeLine, Slot +from PySide6.QtCore import QObject, QTimeLine, Slot from helper.usesqapplication import UsesQApplication @@ -56,4 +56,3 @@ class UserSlotTest(UsesQApplication): if __name__ == '__main__': unittest.main() - diff --git a/sources/pyside6/tests/signals/ref06_test.py b/sources/pyside6/tests/signals/ref06_test.py index 21fbe545..a827131d 100644 --- a/sources/pyside6/tests/signals/ref06_test.py +++ b/sources/pyside6/tests/signals/ref06_test.py @@ -12,7 +12,7 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from init_paths import init_test_paths init_test_paths(False) -from PySide6.QtCore import QObject, QCoreApplication, QTimeLine, Signal, Slot +from PySide6.QtCore import QObject, QTimeLine, Signal, Slot from helper.usesqapplication import UsesQApplication @@ -59,4 +59,3 @@ class SignaltoSignalTest(UsesQApplication): if __name__ == '__main__': unittest.main() - diff --git a/sources/pyside6/tests/signals/segfault_proxyparent_test.py b/sources/pyside6/tests/signals/segfault_proxyparent_test.py index c85ab10c..cb0df097 100644 --- a/sources/pyside6/tests/signals/segfault_proxyparent_test.py +++ b/sources/pyside6/tests/signals/segfault_proxyparent_test.py @@ -11,7 +11,7 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from init_paths import init_test_paths init_test_paths(False) -from PySide6.QtCore import QObject, SIGNAL +from PySide6.QtCore import QObject, Signal # Description of the problem # After creating an PyObject that inherits from QObject, connecting it, @@ -20,16 +20,19 @@ from PySide6.QtCore import QObject, SIGNAL # Somehow the underlying QObject also points to the same position. -# In PyQt4, the connection works fine with the same memory behavior, -# so it looks like specific to SIP. +class Sender(QObject): + + bar = Signal(int) -class Dummy(QObject): def __init__(self, parent=None): QObject.__init__(self, parent) class Joe(QObject): + + bar = Signal(int) + def __init__(self, parent=None): QObject.__init__(self, parent) @@ -44,7 +47,7 @@ class SegfaultCase(unittest.TestCase): def tearDown(self): try: del self.args - except: + except: # noqa: E722 pass # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion gc.collect() @@ -55,22 +58,21 @@ class SegfaultCase(unittest.TestCase): def testSegfault(self): """Regression: Segfault for qobjects in the same memory position.""" - obj = Dummy() - QObject.connect(obj, SIGNAL('bar(int)'), self.callback) + obj = Sender() + obj.bar.connect(self.callback) self.args = (33,) - obj.emit(SIGNAL('bar(int)'), self.args[0]) + obj.bar.emit(self.args[0]) self.assertTrue(self.called) del obj # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion gc.collect() obj = Joe() - QObject.connect(obj, SIGNAL('bar(int)'), self.callback) + obj.bar.connect(self.callback) self.args = (33,) - obj.emit(SIGNAL('bar(int)'), self.args[0]) + obj.bar.emit(self.args[0]) self.assertTrue(self.called) if __name__ == '__main__': unittest.main() - diff --git a/sources/pyside6/tests/signals/self_connect_test.py b/sources/pyside6/tests/signals/self_connect_test.py index d6c03db1..08ca725f 100644 --- a/sources/pyside6/tests/signals/self_connect_test.py +++ b/sources/pyside6/tests/signals/self_connect_test.py @@ -13,17 +13,27 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from init_paths import init_test_paths init_test_paths(False) -from PySide6.QtCore import QObject, SIGNAL, SLOT +from PySide6.QtCore import QObject, Slot from PySide6.QtWidgets import QPushButton, QWidget from helper.usesqapplication import UsesQApplication +class Receiver(QObject): + def __init__(self, p=None): + super().__init__(p) + self.triggered = False + + @Slot(bool, int) + def default_parameter_slot(self, bool_value, int_value=0): + self.triggered = True + + class SelfConnect(UsesQApplication): def testButtonClickClose(self): button = QPushButton() - button.connect(button, SIGNAL('clicked()'), SLOT('close()')) + button.clicked.connect(button.close) button.show() self.assertTrue(button.isVisible()) @@ -33,13 +43,22 @@ class SelfConnect(UsesQApplication): def testWindowButtonClickClose(self): button = QPushButton() window = QWidget() - window.connect(button, SIGNAL('clicked()'), SLOT('close()')) + button.clicked.connect(window.close) window.show() self.assertTrue(window.isVisible()) button.click() self.assertTrue(not window.isVisible()) + def testDefaultParameters(self): + button = QPushButton() + receiver = Receiver(button) + button.clicked.connect(receiver.default_parameter_slot) + button.clicked.connect(button.close) + button.show() + button.click() + self.assertTrue(receiver.triggered) + if __name__ == '__main__': unittest.main() diff --git a/sources/pyside6/tests/signals/short_circuit_test.py b/sources/pyside6/tests/signals/short_circuit_test.py index 43451833..1ad4bc24 100644 --- a/sources/pyside6/tests/signals/short_circuit_test.py +++ b/sources/pyside6/tests/signals/short_circuit_test.py @@ -11,11 +11,17 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from init_paths import init_test_paths init_test_paths(False) -from PySide6.QtCore import QObject, SIGNAL, SLOT +from PySide6.QtCore import QObject, Signal -class Dummy(QObject): - """Dummy class used in this test.""" +class Sender(QObject): + """Sender class used in this test.""" + + foo = Signal() + foo_int = Signal(int) + foo_int_int_string = Signal(int, int, str) + foo_int_qobject = Signal(int, QObject) + def __init__(self, parent=None): QObject.__init__(self, parent) @@ -27,7 +33,7 @@ class ShortCircuitSignals(unittest.TestCase): def tearDown(self): try: del self.args - except: + except: # noqa: E722 pass # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion gc.collect() @@ -38,40 +44,40 @@ class ShortCircuitSignals(unittest.TestCase): def testNoArgs(self): """Short circuit signal without arguments""" - obj1 = Dummy() - QObject.connect(obj1, SIGNAL('foo()'), self.callback) + obj1 = Sender() + obj1.foo.connect(self.callback) self.args = tuple() - obj1.emit(SIGNAL('foo()'), *self.args) + obj1.foo.emit(*self.args) self.assertTrue(self.called) def testWithArgs(self): """Short circuit signal with integer arguments""" - obj1 = Dummy() + obj1 = Sender() - QObject.connect(obj1, SIGNAL('foo(int)'), self.callback) + obj1.foo_int.connect(self.callback) self.args = (42,) - obj1.emit(SIGNAL('foo(int)'), *self.args) + obj1.foo_int.emit(*self.args) self.assertTrue(self.called) def testMultipleArgs(self): """Short circuit signal with multiple arguments""" - obj1 = Dummy() + obj1 = Sender() - QObject.connect(obj1, SIGNAL('foo(int,int,QString)'), self.callback) + obj1.foo_int_int_string.connect(self.callback) self.args = (42, 33, 'char') - obj1.emit(SIGNAL('foo(int,int,QString)'), *self.args) + obj1.foo_int_int_string.emit(*self.args) self.assertTrue(self.called) def testComplexArgs(self): """Short circuit signal with complex arguments""" - obj1 = Dummy() + obj1 = Sender() - QObject.connect(obj1, SIGNAL('foo(int,QObject*)'), self.callback) + obj1.foo_int_qobject.connect(self.callback) self.args = (42, obj1) - obj1.emit(SIGNAL('foo(int,QObject*)'), *self.args) + obj1.foo_int_qobject.emit(*self.args) self.assertTrue(self.called) diff --git a/sources/pyside6/tests/signals/signal2signal_connect_test.py b/sources/pyside6/tests/signals/signal2signal_connect_test.py index c755a9dc..31129f7a 100644 --- a/sources/pyside6/tests/signals/signal2signal_connect_test.py +++ b/sources/pyside6/tests/signals/signal2signal_connect_test.py @@ -13,7 +13,20 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from init_paths import init_test_paths init_test_paths(False) -from PySide6.QtCore import QObject, SIGNAL +from PySide6.QtCore import QObject, Signal + + +class Sender(QObject): + + mysignal_int = Signal(int) + mysignal_int_int = Signal(int, int) + mysignal_string = Signal(str) + + +class Forwarder(Sender): + + forward = Signal() + forward_qobject = Signal(QObject) def cute_slot(): @@ -25,8 +38,8 @@ class TestSignal2SignalConnect(unittest.TestCase): def setUp(self): # Set up the basic resources needed - self.sender = QObject() - self.forwarder = QObject() + self.sender = Sender() + self.forwarder = Forwarder() self.args = None self.called = False @@ -34,11 +47,11 @@ class TestSignal2SignalConnect(unittest.TestCase): # Delete used resources try: del self.sender - except: + except: # noqa: E722 pass try: del self.forwarder - except: + except: # noqa: E722 pass del self.args # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion @@ -63,47 +76,37 @@ class TestSignal2SignalConnect(unittest.TestCase): raise TypeError("Invalid arguments") def testSignalWithoutArguments(self): - QObject.connect(self.sender, SIGNAL("destroyed()"), - self.forwarder, SIGNAL("forward()")) - QObject.connect(self.forwarder, SIGNAL("forward()"), - self.callback_noargs) + self.sender.destroyed.connect(self.forwarder.forward) + self.forwarder.forward.connect(self.callback_noargs) del self.sender # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion gc.collect() self.assertTrue(self.called) def testSignalWithOnePrimitiveTypeArgument(self): - QObject.connect(self.sender, SIGNAL("mysignal(int)"), - self.forwarder, SIGNAL("mysignal(int)")) - QObject.connect(self.forwarder, SIGNAL("mysignal(int)"), - self.callback_args) + self.sender.mysignal_int.connect(self.forwarder.mysignal_int) + self.forwarder.mysignal_int.connect(self.callback_args) self.args = (19,) - self.sender.emit(SIGNAL('mysignal(int)'), *self.args) + self.sender.mysignal_int.emit(*self.args) self.assertTrue(self.called) def testSignalWithMultiplePrimitiveTypeArguments(self): - QObject.connect(self.sender, SIGNAL("mysignal(int,int)"), - self.forwarder, SIGNAL("mysignal(int,int)")) - QObject.connect(self.forwarder, SIGNAL("mysignal(int,int)"), - self.callback_args) + self.sender.mysignal_int_int.connect(self.forwarder.mysignal_int_int) + self.forwarder.mysignal_int_int.connect(self.callback_args) self.args = (23, 29) - self.sender.emit(SIGNAL('mysignal(int,int)'), *self.args) + self.sender.mysignal_int_int.emit(*self.args) self.assertTrue(self.called) def testSignalWithOneStringArgument(self): - QObject.connect(self.sender, SIGNAL("mysignal(QString)"), - self.forwarder, SIGNAL("mysignal(QString)")) - QObject.connect(self.forwarder, SIGNAL("mysignal(QString)"), - self.callback_args) + self.sender.mysignal_string.connect(self.forwarder.mysignal_string) + self.forwarder.mysignal_string.connect(self.callback_args) self.args = ('myargument',) - self.sender.emit(SIGNAL('mysignal(QString)'), *self.args) + self.sender.mysignal_string.emit(*self.args) self.assertTrue(self.called) def testSignalWithOneQObjectArgument(self): - QObject.connect(self.sender, SIGNAL('destroyed(QObject*)'), - self.forwarder, SIGNAL('forward(QObject*)')) - QObject.connect(self.forwarder, SIGNAL('forward(QObject*)'), - self.callback_qobject) + self.sender.destroyed.connect(self.forwarder.forward_qobject) + self.forwarder.forward_qobject.connect(self.callback_qobject) obj_name = 'sender' self.sender.setObjectName(obj_name) @@ -116,5 +119,3 @@ class TestSignal2SignalConnect(unittest.TestCase): if __name__ == '__main__': unittest.main() - - diff --git a/sources/pyside6/tests/signals/signal_autoconnect_test.py b/sources/pyside6/tests/signals/signal_autoconnect_test.py index 820a7744..51d1cea3 100644 --- a/sources/pyside6/tests/signals/signal_autoconnect_test.py +++ b/sources/pyside6/tests/signals/signal_autoconnect_test.py @@ -27,7 +27,7 @@ class MyObject(QWidget): class AutoConnectionTest(unittest.TestCase): def testConnection(self): - app = QApplication([]) + app = QApplication([]) # noqa: F841 win = MyObject() btn = QPushButton("click", win) diff --git a/sources/pyside6/tests/signals/signal_connectiontype_support_test.py b/sources/pyside6/tests/signals/signal_connectiontype_support_test.py index 95ce1fa4..0a69c1e0 100644 --- a/sources/pyside6/tests/signals/signal_connectiontype_support_test.py +++ b/sources/pyside6/tests/signals/signal_connectiontype_support_test.py @@ -10,13 +10,16 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from init_paths import init_test_paths init_test_paths(False) -from PySide6.QtCore import QObject, SIGNAL, Qt +from PySide6.QtCore import QObject, Signal, Qt -class Dummy(QObject): +class Sender(QObject): """Dummy class used in this test.""" + + foo = Signal() + def __init__(self, parent=None): - QObject.__init__(self, parent) + super().__init__(parent) class TestConnectionTypeSupport(unittest.TestCase): @@ -26,11 +29,11 @@ class TestConnectionTypeSupport(unittest.TestCase): def testNoArgs(self): """Connect signal using a Qt.ConnectionType as argument""" - obj1 = Dummy() + obj1 = Sender() - QObject.connect(obj1, SIGNAL('foo()'), self.callback, Qt.DirectConnection) + obj1.foo.connect(self.callback, Qt.DirectConnection) self.args = tuple() - obj1.emit(SIGNAL('foo()'), *self.args) + obj1.foo.emit(*self.args) self.assertTrue(self.called) diff --git a/sources/pyside6/tests/signals/signal_emission_gui_test.py b/sources/pyside6/tests/signals/signal_emission_gui_test.py index aae96f23..5a49b9d1 100644 --- a/sources/pyside6/tests/signals/signal_emission_gui_test.py +++ b/sources/pyside6/tests/signals/signal_emission_gui_test.py @@ -14,112 +14,104 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from init_paths import init_test_paths init_test_paths(False) -from PySide6.QtCore import QObject, SIGNAL, SLOT - -try: - from PySide6.QtWidgets import QSpinBox, QPushButton - hasQtGui = True -except ImportError: - hasQtGui = False +from PySide6.QtWidgets import QSpinBox, QPushButton from helper.basicpyslotcase import BasicPySlotCase from helper.usesqapplication import UsesQApplication -if hasQtGui: - class ButtonPySlot(UsesQApplication, BasicPySlotCase): - """Tests the connection of python slots to QPushButton signals""" - - def testButtonClicked(self): - """Connection of a python slot to QPushButton.clicked()""" - button = QPushButton('Mylabel') - button.clicked.connect(self.cb) - self.args = tuple() - button.emit(SIGNAL('clicked(bool)'), False) - self.assertTrue(self.called) - - def testButtonClick(self): - """Indirect qt signal emission using the QPushButton.click() method """ - button = QPushButton('label') - button.clicked.connect(self.cb) - self.args = tuple() - button.click() - self.assertTrue(self.called) - - -if hasQtGui: - class SpinBoxPySlot(UsesQApplication, BasicPySlotCase): - """Tests the connection of python slots to QSpinBox signals""" - - def setUp(self): - super(SpinBoxPySlot, self).setUp() - self.spin = QSpinBox() - - def tearDown(self): - del self.spin - # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion - gc.collect() - super(SpinBoxPySlot, self).tearDown() - - def testSpinBoxValueChanged(self): - """Connection of a python slot to QSpinBox.valueChanged(int)""" - self.spin.valueChanged.connect(self.cb) - self.args = [3] - self.spin.emit(SIGNAL('valueChanged(int)'), *self.args) - self.assertTrue(self.called) - - def testSpinBoxValueChangedImplicit(self): - """Indirect qt signal emission using QSpinBox.setValue(int)""" - self.spin.valueChanged.connect(self.cb) - self.args = [42] - self.spin.setValue(self.args[0]) - self.assertTrue(self.called) - - def atestSpinBoxValueChangedFewArgs(self): - """Emission of signals with fewer arguments than needed""" - # XXX: PyQt4 crashes on the assertRaises - self.spin.valueChanged.connect(self.cb) - self.args = (554,) - self.assertRaises(TypeError, self.spin.emit, SIGNAL('valueChanged(int)')) - -if hasQtGui: - class QSpinBoxQtSlots(UsesQApplication): - """Tests the connection to QSpinBox qt slots""" - - qapplication = True - - def testSetValueIndirect(self): - """Indirect signal emission: QSpinBox using valueChanged(int)/setValue(int)""" - spinSend = QSpinBox() - spinRec = QSpinBox() - - spinRec.setValue(5) - - spinSend.valueChanged.connect(spinRec.setValue) - self.assertEqual(spinRec.value(), 5) - spinSend.setValue(3) - self.assertEqual(spinRec.value(), 3) - self.assertEqual(spinSend.value(), 3) - - def testSetValue(self): - """Direct signal emission: QSpinBox using valueChanged(int)/setValue(int)""" - spinSend = QSpinBox() - spinRec = QSpinBox() - - spinRec.setValue(5) - spinSend.setValue(42) - - spinSend.valueChanged.connect(spinRec.setValue) - self.assertEqual(spinRec.value(), 5) - self.assertEqual(spinSend.value(), 42) - spinSend.emit(SIGNAL('valueChanged(int)'), 3) - - self.assertEqual(spinRec.value(), 3) - # Direct emission shouldn't change the value of the emitter - self.assertEqual(spinSend.value(), 42) - - spinSend.emit(SIGNAL('valueChanged(int)'), 66) - self.assertEqual(spinRec.value(), 66) - self.assertEqual(spinSend.value(), 42) + +class ButtonPySlot(UsesQApplication, BasicPySlotCase): + """Tests the connection of python slots to QPushButton signals""" + + def testButtonClicked(self): + """Connection of a python slot to QPushButton.clicked()""" + button = QPushButton('Mylabel') + button.clicked.connect(self.cb) + self.args = tuple() + button.clicked.emit() + self.assertTrue(self.called) + + def testButtonClick(self): + """Indirect qt signal emission using the QPushButton.click() method """ + button = QPushButton('label') + button.clicked.connect(self.cb) + self.args = tuple() + button.click() + self.assertTrue(self.called) + + +class SpinBoxPySlot(UsesQApplication, BasicPySlotCase): + """Tests the connection of python slots to QSpinBox signals""" + + def setUp(self): + super(SpinBoxPySlot, self).setUp() + self.spin = QSpinBox() + + def tearDown(self): + del self.spin + # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion + gc.collect() + super(SpinBoxPySlot, self).tearDown() + + def testSpinBoxValueChanged(self): + """Connection of a python slot to QSpinBox.valueChanged(int)""" + self.spin.valueChanged.connect(self.cb) + self.args = [3] + self.spin.valueChanged.emit(*self.args) + self.assertTrue(self.called) + + def testSpinBoxValueChangedImplicit(self): + """Indirect qt signal emission using QSpinBox.setValue(int)""" + self.spin.valueChanged.connect(self.cb) + self.args = [42] + self.spin.setValue(self.args[0]) + self.assertTrue(self.called) + + def atestSpinBoxValueChangedFewArgs(self): + """Emission of signals with fewer arguments than needed""" + self.spin.valueChanged.connect(self.cb) + self.args = (554,) + self.assertRaises(TypeError, self.spin.valueChanged.emit) + + +class QSpinBoxQtSlots(UsesQApplication): + """Tests the connection to QSpinBox qt slots""" + + qapplication = True + + def testSetValueIndirect(self): + """Indirect signal emission: QSpinBox using valueChanged(int)/setValue(int)""" + spinSend = QSpinBox() + spinRec = QSpinBox() + + spinRec.setValue(5) + + spinSend.valueChanged.connect(spinRec.setValue) + self.assertEqual(spinRec.value(), 5) + spinSend.setValue(3) + self.assertEqual(spinRec.value(), 3) + self.assertEqual(spinSend.value(), 3) + + def testSetValue(self): + """Direct signal emission: QSpinBox using valueChanged(int)/setValue(int)""" + spinSend = QSpinBox() + spinRec = QSpinBox() + + spinRec.setValue(5) + spinSend.setValue(42) + + spinSend.valueChanged.connect(spinRec.setValue) + self.assertEqual(spinRec.value(), 5) + self.assertEqual(spinSend.value(), 42) + spinSend.valueChanged.emit(3) + + self.assertEqual(spinRec.value(), 3) + # Direct emission shouldn't change the value of the emitter + self.assertEqual(spinSend.value(), 42) + + spinSend.valueChanged.emit(66) + self.assertEqual(spinRec.value(), 66) + self.assertEqual(spinSend.value(), 42) if __name__ == '__main__': diff --git a/sources/pyside6/tests/signals/signal_emission_test.py b/sources/pyside6/tests/signals/signal_emission_test.py index 1439432e..b31d89c2 100644 --- a/sources/pyside6/tests/signals/signal_emission_test.py +++ b/sources/pyside6/tests/signals/signal_emission_test.py @@ -14,9 +14,8 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from init_paths import init_test_paths init_test_paths(False) -from PySide6.QtCore import QObject, SIGNAL, SLOT, QProcess, QTimeLine +from PySide6.QtCore import QObject, Signal, SIGNAL, QProcess, QTimeLine -from helper.basicpyslotcase import BasicPySlotCase from helper.usesqapplication import UsesQApplication @@ -26,7 +25,7 @@ class ArgsOnEmptySignal(UsesQApplication): def testArgsToNoArgsSignal(self): '''Passing arguments to a signal without arguments''' process = QProcess() - self.assertRaises(TypeError, process.emit, SIGNAL('started()'), 42) + self.assertRaises(TypeError, process.started.emit, 42) class MoreArgsOnEmit(UsesQApplication): @@ -35,12 +34,14 @@ class MoreArgsOnEmit(UsesQApplication): def testMoreArgs(self): '''Passing more arguments than needed''' process = QProcess() - self.assertRaises(TypeError, process.emit, SIGNAL('finished(int)'), 55, 55) + self.assertRaises(TypeError, process.finished.emit, 55, QProcess.ExitStatus.NormalExit, 42) -class Dummy(QObject): - '''Dummy class''' - pass +class Sender(QObject): + '''Sender class''' + + dummy = Signal() + dummy_int = Signal(int) class PythonSignalToCppSlots(UsesQApplication): @@ -49,12 +50,11 @@ class PythonSignalToCppSlots(UsesQApplication): def testWithoutArgs(self): '''Connect python signal to QTimeLine.toggleDirection()''' timeline = QTimeLine() - dummy = Dummy() - QObject.connect(dummy, SIGNAL('dummy()'), - timeline, SLOT('toggleDirection()')) + sender = Sender() + sender.dummy.connect(timeline.toggleDirection) orig_dir = timeline.direction() - dummy.emit(SIGNAL('dummy()')) + sender.dummy.emit() new_dir = timeline.direction() if orig_dir == QTimeLine.Forward: @@ -65,13 +65,12 @@ class PythonSignalToCppSlots(UsesQApplication): def testWithArgs(self): '''Connect python signals to QTimeLine.setCurrentTime(int)''' timeline = QTimeLine() - dummy = Dummy() + sender = Sender() - QObject.connect(dummy, SIGNAL('dummy(int)'), - timeline, SLOT('setCurrentTime(int)')) + sender.dummy_int.connect(timeline.setCurrentTime) current = timeline.currentTime() - dummy.emit(SIGNAL('dummy(int)'), current + 42) + sender.dummy_int.emit(current + 42) self.assertEqual(timeline.currentTime(), current + 42) @@ -83,13 +82,13 @@ class CppSignalsToCppSlots(UsesQApplication): process = QProcess() timeline = QTimeLine() - QObject.connect(process, SIGNAL('finished(int, QProcess::ExitStatus)'), - timeline, SLOT('toggleDirection()')) + process.finished.connect(timeline.toggleDirection) orig_dir = timeline.direction() process.start(sys.executable, ['-c', '"print 42"']) - process.waitForFinished() + self.assertTrue(process.waitForStarted()) + self.assertTrue(process.waitForFinished()) new_dir = timeline.direction() @@ -112,9 +111,9 @@ class DynamicSignalsToFuncPartial(UsesQApplication): def testIt(self): global called called = False - o = QObject() - o.connect(o, SIGNAL("ASignal()"), functools.partial(someSlot, "partial ..")) - o.emit(SIGNAL("ASignal()")) + o = Sender() + o.dummy.connect(functools.partial(someSlot, "partial ..")) + o.dummy.emit() self.assertTrue(called) diff --git a/sources/pyside6/tests/signals/signal_manager_refcount_test.py b/sources/pyside6/tests/signals/signal_manager_refcount_test.py index 31d3bc85..955d5b65 100644 --- a/sources/pyside6/tests/signals/signal_manager_refcount_test.py +++ b/sources/pyside6/tests/signals/signal_manager_refcount_test.py @@ -12,11 +12,12 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from init_paths import init_test_paths init_test_paths(False) -from PySide6.QtCore import QObject, SIGNAL +from PySide6.QtCore import QObject class SignalManagerRefCount(unittest.TestCase): - """Simple test case to check if the signal_manager is erroneously incrementing the object refcounter""" + """Simple test case to check if the signal_manager is erroneously incrementing the + object refcounter.""" @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount") def testObjectRefcount(self): @@ -27,10 +28,9 @@ class SignalManagerRefCount(unittest.TestCase): refcount = sys.getrefcount(obj) obj.destroyed.connect(callback) self.assertEqual(refcount, sys.getrefcount(obj)) - QObject.disconnect(obj, SIGNAL('destroyed()'), callback) + obj.destroyed.disconnect(callback) self.assertEqual(refcount, sys.getrefcount(obj)) if __name__ == '__main__': unittest.main() - diff --git a/sources/pyside6/tests/signals/signal_signature_test.py b/sources/pyside6/tests/signals/signal_signature_test.py index 59bc1b29..e8f08b2d 100644 --- a/sources/pyside6/tests/signals/signal_signature_test.py +++ b/sources/pyside6/tests/signals/signal_signature_test.py @@ -20,6 +20,10 @@ called = False name = "Old" +class Sender(QObject): + dummySignal = Signal() + + class Obj(QObject): dummySignalArgs = Signal(str) numberSignal = Signal(int) @@ -78,9 +82,9 @@ class TestConnectNotifyWithNewStyleSignals(UsesQApplication): def testStaticSlot(self): global called - sender = Obj() - sender.connect(sender, SIGNAL("dummySignal()"), Obj.static_method) - sender.emit(SIGNAL("dummySignal()")) + sender = Sender() + sender.dummySignal.connect(Obj.static_method) + sender.dummySignal.emit() self.assertTrue(called) def testStaticSlotArgs(self): @@ -99,4 +103,3 @@ class TestConnectNotifyWithNewStyleSignals(UsesQApplication): if __name__ == '__main__': unittest.main() - diff --git a/sources/pyside6/tests/signals/signal_with_primitive_type_test.py b/sources/pyside6/tests/signals/signal_with_primitive_type_test.py index a4d5f33a..01492b33 100644 --- a/sources/pyside6/tests/signals/signal_with_primitive_type_test.py +++ b/sources/pyside6/tests/signals/signal_with_primitive_type_test.py @@ -10,7 +10,7 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from init_paths import init_test_paths init_test_paths(False) -from PySide6.QtCore import QCoreApplication, QObject, QTimeLine, SIGNAL +from PySide6.QtCore import QCoreApplication, QTimeLine class SignalPrimitiveTypeTest(unittest.TestCase): @@ -36,5 +36,3 @@ class SignalPrimitiveTypeTest(unittest.TestCase): if __name__ == '__main__': unittest.main() - - diff --git a/sources/pyside6/tests/signals/signals.pyproject b/sources/pyside6/tests/signals/signals.pyproject new file mode 100644 index 00000000..b63724ea --- /dev/null +++ b/sources/pyside6/tests/signals/signals.pyproject @@ -0,0 +1,19 @@ +{ + "files": ["anonymous_slot_leak_test.py", "args_dont_match_test.py", + "bug_189.py", "bug_311.py", "bug_312.py", "bug_319.py", "bug_79.py", + "decorators_test.py", "disconnect_test.py", "invalid_callback_test.py", + "lambda_gui_test.py", "lambda_test.py", "leaking_signal_test.py", + "multiple_connections_gui_test.py", "multiple_connections_test.py", + "pysignal_test.py", "qobject_callable_connect_test.py", "qobject_destroyed_test.py", + "qobject_receivers_test.py", "qobject_sender_test.py", "ref01_test.py", + "ref02_test.py", "ref03_test.py", "ref04_test.py", "ref05_test.py", + "ref06_test.py", "segfault_proxyparent_test.py", + "self_connect_test.py", "short_circuit_test.py", + "signal2signal_connect_test.py", "signal_across_threads.py", + "signal_autoconnect_test.py", "signal_connectiontype_support_test.py", + "signal_emission_gui_test.py", "signal_emission_test.py", + "signal_enum_test.py", "signal_func_test.py", "signal_manager_refcount_test.py", + "signal_newenum_test.py", "signal_number_limit_test.py", + "signal_object_test.py", "signal_signature_test.py", "signal_with_primitive_type_test.py", + "slot_reference_count_test.py", "static_metaobject_test.py"] +} diff --git a/sources/pyside6/tests/signals/slot_reference_count_test.py b/sources/pyside6/tests/signals/slot_reference_count_test.py index 10a40597..9d5c7365 100644 --- a/sources/pyside6/tests/signals/slot_reference_count_test.py +++ b/sources/pyside6/tests/signals/slot_reference_count_test.py @@ -12,12 +12,14 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from init_paths import init_test_paths init_test_paths(False) -from PySide6.QtCore import QObject, SIGNAL, SLOT +from PySide6.QtCore import QObject, Signal class Dummy(QObject): + foo = Signal() + def dispatch(self): - self.emit(SIGNAL('foo()')) + self.foo.emit() class PythonSignalRefCount(unittest.TestCase): @@ -35,10 +37,10 @@ class PythonSignalRefCount(unittest.TestCase): self.assertEqual(sys.getrefcount(cb), 2) - QObject.connect(self.emitter, SIGNAL('foo()'), cb) + self.emitter.foo.connect(cb) self.assertEqual(sys.getrefcount(cb), 3) - QObject.disconnect(self.emitter, SIGNAL('foo()'), cb) + self.emitter.foo.disconnect(cb) self.assertEqual(sys.getrefcount(cb), 2) @@ -60,7 +62,7 @@ class CppSignalRefCount(unittest.TestCase): self.emitter.destroyed.connect(cb) self.assertEqual(sys.getrefcount(cb), 3) - QObject.disconnect(self.emitter, SIGNAL('destroyed()'), cb) + self.emitter.destroyed.disconnect(cb) self.assertEqual(sys.getrefcount(cb), 2) diff --git a/sources/pyside6/tests/signals/static_metaobject_test.py b/sources/pyside6/tests/signals/static_metaobject_test.py index 6b1372d5..d7bf73e4 100644 --- a/sources/pyside6/tests/signals/static_metaobject_test.py +++ b/sources/pyside6/tests/signals/static_metaobject_test.py @@ -14,13 +14,22 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from init_paths import init_test_paths init_test_paths(False) -from PySide6.QtCore import QObject, SIGNAL, Slot +from PySide6.QtCore import QObject, Signal, Slot, SIGNAL from helper.usesqapplication import UsesQApplication +class Sender(QObject): + + foo = Signal() + foo2 = Signal() + + class MyObject(QObject): + + foo2 = Signal() + def __init__(self, parent=None): - QObject.__init__(self, parent) + super().__init__(parent) self._slotCalledCount = 0 # this '@Slot()' is needed to get the right sort order in testSharedSignalEmission. @@ -33,7 +42,8 @@ class MyObject(QObject): class StaticMetaObjectTest(UsesQApplication): def testSignalPropagation(self): - o = MyObject() + """Old style, dynamic signal creation.""" + o = QObject() o2 = MyObject() # SIGNAL foo not created yet @@ -55,17 +65,17 @@ class StaticMetaObjectTest(UsesQApplication): self.assertEqual(o.metaObject().indexOfSignal("foo()"), -1) def testSharedSignalEmission(self): - o = QObject() + o = Sender() m = MyObject() - o.connect(SIGNAL("foo2()"), m.mySlot) - m.connect(SIGNAL("foo2()"), m.mySlot) - o.emit(SIGNAL("foo2()")) + o.foo2.connect(m.mySlot) + m.foo2.connect(m.mySlot) + o.foo2.emit() self.assertEqual(m._slotCalledCount, 1) del o # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion gc.collect() - m.emit(SIGNAL("foo2()")) + m.foo2.emit() self.assertEqual(m._slotCalledCount, 2) diff --git a/sources/pyside6/tests/tools/pyside6-android-deploy/test_pyside6_android_deploy.py b/sources/pyside6/tests/tools/pyside6-android-deploy/test_pyside6_android_deploy.py index 81c2c9a4..ec575e92 100644 --- a/sources/pyside6/tests/tools/pyside6-android-deploy/test_pyside6_android_deploy.py +++ b/sources/pyside6/tests/tools/pyside6-android-deploy/test_pyside6_android_deploy.py @@ -14,7 +14,7 @@ from unittest import mock from unittest.mock import patch sys.path.append(os.fspath(Path(__file__).resolve().parents[2])) -from init_paths import init_test_paths +from init_paths import init_test_paths # noqa: E402 init_test_paths(False) @@ -75,17 +75,29 @@ class TestPySide6AndroidDeployWidgets(DeployTestBase): self.config_file = self.temp_example / "pysidedeploy.spec" self.buildozer_config = self.temp_example / "buildozer.spec" - def test_dry_run(self, mock_extract_jar): + @patch("deploy_lib.android.android_config.AndroidConfig._find_local_libs") + @patch("deploy_lib.android.android_config.AndroidConfig._find_dependent_qt_modules") + @patch("deploy_lib.android.android_config.find_qtlibs_in_wheel") + def test_dry_run(self, mock_qtlibs, mock_extraqtmodules, mock_local_libs, mock_extract_jar): + mock_qtlibs.return_value = self.pyside_wheel / "PySide6/Qt/lib" + mock_extraqtmodules.return_value = [] + dependent_plugins = ["platforms_qtforandroid", + "platforminputcontexts_qtvirtualkeyboardplugin", + "iconengines_qsvgicon"] + mock_local_libs.return_value = [], dependent_plugins self.android_deploy.main(name="android_app", shiboken_wheel=self.shiboken_wheel, pyside_wheel=self.pyside_wheel, ndk_path=self.ndk_path, dry_run=True, force=True) + self.assertEqual(mock_extract_jar.call_count, 0) + self.assertEqual(mock_qtlibs.call_count, 1) + self.assertEqual(mock_extraqtmodules.call_count, 1) + self.assertEqual(mock_local_libs.call_count, 1) @patch("deploy_lib.android.buildozer.BuildozerConfig._BuildozerConfig__find_jars") @patch("deploy_lib.android.android_config.AndroidConfig.recipes_exist") - @patch("deploy_lib.android.buildozer.BuildozerConfig." - "_BuildozerConfig__find_dependent_qt_modules") - @patch("deploy_lib.android.buildozer.find_qtlibs_in_wheel") + @patch("deploy_lib.android.android_config.AndroidConfig._find_dependent_qt_modules") + @patch("deploy_lib.android.android_config.find_qtlibs_in_wheel") def test_config(self, mock_qtlibs, mock_extraqtmodules, mock_recipes_exist, mock_find_jars, mock_extract_jar): jar_dir = "tmp/jar/PySide6/jar" @@ -126,9 +138,9 @@ class TestPySide6AndroidDeployWidgets(DeployTestBase): self.assertIn(str(self.ndk_path), config_obj.get_value("buildozer", "ndk_path")) self.assertEqual(config_obj.get_value("buildozer", "sdk_path"), '') expected_modules = {"Core", "Gui"} - obtained_modules = set(config_obj.get_value("buildozer", "modules").split(",")) + obtained_modules = set(config_obj.get_value("qt", "modules").split(",")) self.assertEqual(obtained_modules, expected_modules) - expected_local_libs = "plugins_platforms_qtforandroid" + expected_local_libs = "" self.assertEqual(config_obj.get_value("buildozer", "local_libs"), expected_local_libs) self.assertEqual(config_obj.get_value("buildozer", "arch"), "x86_64") @@ -189,12 +201,11 @@ class TestPySide6AndroidDeployQml(DeployTestBase): (self.temp_qml_example / "stringlistmodel.py").rename(self.temp_qml_example / "main.py") (self.temp_qml_example / "stringlistmodel.pyproject").unlink() - @patch("deploy_lib.android.buildozer.BuildozerConfig._BuildozerConfig__find_local_libs") + @patch("deploy_lib.android.android_config.AndroidConfig._find_local_libs") @patch("deploy_lib.android.buildozer.BuildozerConfig._BuildozerConfig__find_jars") @patch("deploy_lib.android.android_config.AndroidConfig.recipes_exist") - @patch("deploy_lib.android.buildozer.BuildozerConfig." - "_BuildozerConfig__find_dependent_qt_modules") - @patch("deploy_lib.android.buildozer.find_qtlibs_in_wheel") + @patch("deploy_lib.android.android_config.AndroidConfig._find_dependent_qt_modules") + @patch("deploy_lib.android.android_config.find_qtlibs_in_wheel") def test_config_with_Qml(self, mock_qtlibs, mock_extraqtmodules, mock_recipes_exist, mock_find_jars, mock_local_libs, mock_extract_jar, mock_qmlimportscanner): @@ -228,12 +239,11 @@ class TestPySide6AndroidDeployQml(DeployTestBase): self.assertTrue(self.config_file.exists()) self.assertTrue(self.buildozer_config_file.exists()) - # test config file contents config_obj = self.deploy_lib.BaseConfig(config_file=self.config_file) expected_modules = {"Quick", "Core", "Gui", "Network", "Qml", "QmlModels", "OpenGL"} - obtained_modules = set(config_obj.get_value("buildozer", "modules").split(",")) + obtained_modules = set(config_obj.get_value("qt", "modules").split(",")) self.assertEqual(obtained_modules, expected_modules) - expected_local_libs = "plugins_platforms_qtforandroid" + expected_local_libs = "" self.assertEqual(config_obj.get_value("buildozer", "local_libs"), expected_local_libs) expected_qt_plugins = set(dependent_plugins) diff --git a/sources/pyside6/tests/tools/pyside6-deploy/test_pyside6_deploy.py b/sources/pyside6/tests/tools/pyside6-deploy/test_pyside6_deploy.py index 9c743674..f84eec1e 100644 --- a/sources/pyside6/tests/tools/pyside6-deploy/test_pyside6_deploy.py +++ b/sources/pyside6/tests/tools/pyside6-deploy/test_pyside6_deploy.py @@ -7,10 +7,15 @@ import shutil import sys import os import importlib +import platform from pathlib import Path from unittest.mock import patch from unittest import mock +sys.path.append(os.fspath(Path(__file__).resolve().parents[2])) +from init_paths import init_test_paths, _get_qt_lib_dir # noqa: E402 +init_test_paths(False) + def is_pyenv_python(): pyenv_root = os.environ.get("PYENV_ROOT") @@ -58,6 +63,9 @@ class DeployTestBase(LongSortedOptionTest): cls.deploy_lib = importlib.import_module("deploy_lib") cls.deploy = importlib.import_module("deploy") sys.modules["deploy"] = cls.deploy + files_to_ignore = [".cpp.o", ".qsb", ".webp"] + cls.dlls_ignore_nuitka = " ".join([f"--noinclude-dlls=*{file}" + for file in files_to_ignore]) # required for comparing long strings cls.maxDiff = None @@ -74,6 +82,9 @@ class DeployTestBase(LongSortedOptionTest): os.chdir(self.current_dir) +@unittest.skipIf(sys.platform == "darwin" and int(platform.mac_ver()[0].split('.')[0]) <= 11, + "Test only works on macOS version 12+") +@patch("deploy_lib.config.QtDependencyReader.find_plugin_dependencies") class TestPySide6DeployWidgets(DeployTestBase): @classmethod def setUpClass(cls): @@ -87,32 +98,45 @@ class TestPySide6DeployWidgets(DeployTestBase): os.chdir(self.temp_example_widgets) self.main_file = self.temp_example_widgets / "tetrix.py" self.deployment_files = self.temp_example_widgets / "deployment" + # All the plugins included. This is different from plugins_nuitka, because Nuitka bundles + # some plugins by default + self.all_plugins = ["accessiblebridge", "egldeviceintegrations", "generic", "iconengines", + "imageformats", "platforminputcontexts", "platforms", + "platforms/darwin", "platformthemes", "styles", "xcbglintegrations"] + # Plugins that needs to be passed to Nuitka + plugins_nuitka = ("accessiblebridge,platforminputcontexts,platforms/darwin") self.expected_run_cmd = ( - f"{sys.executable} -m nuitka {str(self.main_file)} --follow-imports --onefile" + f"{sys.executable} -m nuitka {str(self.main_file)} --follow-imports" f" --enable-plugin=pyside6 --output-dir={str(self.deployment_files)} --quiet" f" --noinclude-qt-translations" + f" --include-qt-plugins={plugins_nuitka}" + f" {self.dlls_ignore_nuitka}" ) if sys.platform.startswith("linux"): - self.expected_run_cmd += f" --linux-icon={str(self.linux_icon)}" + self.expected_run_cmd += f" --linux-icon={str(self.linux_icon)} --onefile" elif sys.platform == "darwin": - self.expected_run_cmd += f" --macos-app-icon={str(self.macos_icon)}" + self.expected_run_cmd += (f" --macos-app-icon={str(self.macos_icon)}" + " --macos-create-app-bundle --standalone") elif sys.platform == "win32": - self.expected_run_cmd += f" --windows-icon-from-ico={str(self.win_icon)}" + self.expected_run_cmd += f" --windows-icon-from-ico={str(self.win_icon)} --onefile" if is_pyenv_python(): self.expected_run_cmd += " --static-libpython=no" self.config_file = self.temp_example_widgets / "pysidedeploy.spec" - def testWidgetDryRun(self): + def testWidgetDryRun(self, mock_plugins): + mock_plugins.return_value = self.all_plugins # Checking for dry run commands is equivalent to mocking the # subprocess.check_call() in commands.py as the the dry run command # is the command being run. original_output = self.deploy.main(self.main_file, dry_run=True, force=True) self.assertEqual(original_output, self.expected_run_cmd) - def testWidgetConfigFile(self): + @patch("deploy_lib.dependency_util.QtDependencyReader.get_qt_libs_dir") + def testWidgetConfigFile(self, mock_sitepackages, mock_plugins): + mock_sitepackages.return_value = Path(_get_qt_lib_dir()) + mock_plugins.return_value = self.all_plugins # includes both dry run and config_file tests - # init init_result = self.deploy.main(self.main_file, init=True, force=True) self.assertEqual(init_result, None) @@ -127,15 +151,23 @@ class TestPySide6DeployWidgets(DeployTestBase): self.assertEqual(config_obj.get_value("app", "project_dir"), ".") self.assertEqual(config_obj.get_value("app", "exec_directory"), ".") self.assertEqual(config_obj.get_value("python", "packages"), - "nuitka==1.8.0,ordered_set,zstandard") + "Nuitka==2.3.2") self.assertEqual(config_obj.get_value("qt", "qml_files"), "") equ_base = "--quiet --noinclude-qt-translations" equ_value = equ_base + " --static-libpython=no" if is_pyenv_python() else equ_base self.assertEqual(config_obj.get_value("nuitka", "extra_args"), equ_value) self.assertEqual(config_obj.get_value("qt", "excluded_qml_plugins"), "") + expected_modules = {"Core", "Gui", "Widgets"} + if sys.platform != "win32": + expected_modules.add("DBus") + obtained_modules = set(config_obj.get_value("qt", "modules").split(",")) + self.assertEqual(obtained_modules, expected_modules) + obtained_qt_plugins = config_obj.get_value("qt", "plugins").split(",") + self.assertEqual(obtained_qt_plugins.sort(), self.all_plugins.sort()) self.config_file.unlink() - def testErrorReturns(self): + def testErrorReturns(self, mock_plugins): + mock_plugins.return_value = self.all_plugins # main file and config file does not exists fake_main_file = self.main_file.parent / "main.py" with self.assertRaises(RuntimeError) as context: @@ -143,6 +175,9 @@ class TestPySide6DeployWidgets(DeployTestBase): self.assertTrue("Directory does not contain main.py file." in str(context.exception)) +@unittest.skipIf(sys.platform == "darwin" and int(platform.mac_ver()[0].split('.')[0]) <= 11, + "Test only works on macOS version 12+") +@patch("deploy_lib.config.QtDependencyReader.find_plugin_dependencies") class TestPySide6DeployQml(DeployTestBase): @classmethod def setUpClass(cls): @@ -158,13 +193,27 @@ class TestPySide6DeployQml(DeployTestBase): self.deployment_files = self.temp_example_qml / "deployment" self.first_qml_file = "main.qml" self.second_qml_file = "MovingRectangle.qml" + + # All the plugins included. This is different from plugins_nuitka, because Nuitka bundles + # some plugins by default + self.all_plugins = ["accessiblebridge", "egldeviceintegrations", "generic", "iconengines", + "imageformats", "networkaccess", "networkinformation", + "platforminputcontexts", "platforms", "platforms/darwin", + "platformthemes", "qmltooling", "scenegraph", "tls", + "xcbglintegrations"] + # Plugins that needs to be passed to Nuitka + plugins_nuitka = ("accessiblebridge,networkaccess,networkinformation,platforminputcontexts," + "platforms/darwin,qml,qmltooling,scenegraph") self.expected_run_cmd = ( - f"{sys.executable} -m nuitka {str(self.main_file)} --follow-imports --onefile" + f"{sys.executable} -m nuitka {str(self.main_file)} --follow-imports" f" --enable-plugin=pyside6 --output-dir={str(self.deployment_files)} --quiet" - f" --noinclude-qt-translations --include-qt-plugins=all" + f" --noinclude-qt-translations" + f" {self.dlls_ignore_nuitka}" + " --noinclude-dlls=*/qml/QtQuickEffectMaker/*" + f" --include-qt-plugins={plugins_nuitka}" f" --include-data-files={str(self.temp_example_qml / self.first_qml_file)}=" f"./main.qml --include-data-files=" - f"{str(self.temp_example_qml /self.second_qml_file)}=./MovingRectangle.qml" + f"{str(self.temp_example_qml / self.second_qml_file)}=./MovingRectangle.qml" ) if sys.platform != "win32": @@ -181,17 +230,21 @@ class TestPySide6DeployQml(DeployTestBase): ) if sys.platform.startswith("linux"): - self.expected_run_cmd += f" --linux-icon={str(self.linux_icon)}" + self.expected_run_cmd += f" --linux-icon={str(self.linux_icon)} --onefile" elif sys.platform == "darwin": - self.expected_run_cmd += f" --macos-app-icon={str(self.macos_icon)}" + self.expected_run_cmd += (f" --macos-app-icon={str(self.macos_icon)}" + " --macos-create-app-bundle --standalone") elif sys.platform == "win32": - self.expected_run_cmd += f" --windows-icon-from-ico={str(self.win_icon)}" + self.expected_run_cmd += f" --windows-icon-from-ico={str(self.win_icon)} --onefile" if is_pyenv_python(): self.expected_run_cmd += " --static-libpython=no" self.config_file = self.temp_example_qml / "pysidedeploy.spec" - def testQmlConfigFile(self): + @patch("deploy_lib.dependency_util.QtDependencyReader.get_qt_libs_dir") + def testQmlConfigFile(self, mock_sitepackages, mock_plugins): + mock_sitepackages.return_value = Path(_get_qt_lib_dir()) + mock_plugins.return_value = self.all_plugins # create config file with patch("deploy_lib.config.run_qmlimportscanner") as mock_qmlimportscanner: mock_qmlimportscanner.return_value = ["QtQuick"] @@ -204,7 +257,7 @@ class TestPySide6DeployQml(DeployTestBase): self.assertEqual(config_obj.get_value("app", "project_dir"), ".") self.assertEqual(config_obj.get_value("app", "exec_directory"), ".") self.assertEqual(config_obj.get_value("python", "packages"), - "nuitka==1.8.0,ordered_set,zstandard") + "Nuitka==2.3.2") self.assertEqual(config_obj.get_value("qt", "qml_files"), "main.qml,MovingRectangle.qml") equ_base = "--quiet --noinclude-qt-translations" equ_value = equ_base + " --static-libpython=no" if is_pyenv_python() else equ_base @@ -213,16 +266,25 @@ class TestPySide6DeployQml(DeployTestBase): config_obj.get_value("qt", "excluded_qml_plugins"), "QtCharts,QtQuick3D,QtSensors,QtTest,QtWebEngine", ) + expected_modules = {"Core", "Gui", "Qml", "Quick", "Network", "OpenGL", "QmlModels"} + if sys.platform != "win32": + expected_modules.add("DBus") + obtained_modules = set(config_obj.get_value("qt", "modules").split(",")) + self.assertEqual(obtained_modules, expected_modules) + obtained_qt_plugins = config_obj.get_value("qt", "plugins").split(",") + self.assertEqual(obtained_qt_plugins.sort(), self.all_plugins.sort()) self.config_file.unlink() - def testQmlDryRun(self): + def testQmlDryRun(self, mock_plugins): + mock_plugins.return_value = self.all_plugins with patch("deploy_lib.config.run_qmlimportscanner") as mock_qmlimportscanner: mock_qmlimportscanner.return_value = ["QtQuick"] original_output = self.deploy.main(self.main_file, dry_run=True, force=True) self.assertEqual(original_output, self.expected_run_cmd) self.assertEqual(mock_qmlimportscanner.call_count, 1) - def testMainFileDryRun(self): + def testMainFileDryRun(self, mock_plugins): + mock_plugins.return_value = self.all_plugins with patch("deploy_lib.config.run_qmlimportscanner") as mock_qmlimportscanner: mock_qmlimportscanner.return_value = ["QtQuick"] original_output = self.deploy.main(Path.cwd() / "main.py", dry_run=True, force=True) @@ -230,6 +292,8 @@ class TestPySide6DeployQml(DeployTestBase): self.assertEqual(mock_qmlimportscanner.call_count, 1) +@unittest.skipIf(sys.platform == "darwin" and int(platform.mac_ver()[0].split('.')[0]) <= 11, + "Test only works on macOS version 12+") class TestPySide6DeployWebEngine(DeployTestBase): @classmethod def setUpClass(cls): @@ -239,12 +303,24 @@ class TestPySide6DeployWebEngine(DeployTestBase): shutil.copytree(example_webenginequick, Path(cls.temp_dir) / "nanobrowser") ).resolve() - # this test case retains the QtWebEngine dlls - def testWebEngineQuickDryRun(self): + @patch("deploy_lib.config.QtDependencyReader.find_plugin_dependencies") + @patch("deploy_lib.dependency_util.QtDependencyReader.get_qt_libs_dir") + def testWebEngineQuickDryRun(self, mock_sitepackages, mock_plugins): + mock_sitepackages.return_value = Path(_get_qt_lib_dir()) + all_plugins = ["accessiblebridge", "egldeviceintegrations", "generic", "iconengines", + "imageformats", "networkaccess", "networkinformation", + "platforminputcontexts", "platforms", "platforms/darwin", + "platformthemes", "qmltooling", "scenegraph", "tls", + "xcbglintegrations"] + mock_plugins.return_value = all_plugins + # this test case retains the QtWebEngine dlls # setup os.chdir(self.temp_example_webenginequick) main_file = self.temp_example_webenginequick / "quicknanobrowser.py" deployment_files = self.temp_example_webenginequick / "deployment" + # Plugins that needs to be passed to Nuitka + plugins_nuitka = ("accessiblebridge,networkaccess,networkinformation,platforminputcontexts," + "platforms/darwin,qml,qmltooling,scenegraph") qml_files = [ "ApplicationRoot.qml", "BrowserDialog.qml", @@ -255,15 +331,18 @@ class TestPySide6DeployWebEngine(DeployTestBase): ] data_files_cmd = " ".join( [ - f"--include-data-files={str(self.temp_example_webenginequick/file)}=./{file}" + f"--include-data-files={str(self.temp_example_webenginequick / file)}=./{file}" for file in qml_files ] ) expected_run_cmd = ( - f"{sys.executable} -m nuitka {str(main_file)} --follow-imports --onefile" + f"{sys.executable} -m nuitka {str(main_file)} --follow-imports" f" --enable-plugin=pyside6 --output-dir={str(deployment_files)} --quiet" f" --noinclude-qt-translations --include-qt-plugins=all" f" {data_files_cmd}" + f" --include-qt-plugins={plugins_nuitka}" + f" {self.dlls_ignore_nuitka}" + " --noinclude-dlls=*/qml/QtQuickEffectMaker/*" ) if sys.platform != "win32": @@ -280,11 +359,12 @@ class TestPySide6DeployWebEngine(DeployTestBase): ) if sys.platform.startswith("linux"): - expected_run_cmd += f" --linux-icon={str(self.linux_icon)}" + expected_run_cmd += f" --linux-icon={str(self.linux_icon)} --onefile" elif sys.platform == "darwin": - expected_run_cmd += f" --macos-app-icon={str(self.macos_icon)}" + expected_run_cmd += (f" --macos-app-icon={str(self.macos_icon)}" + " --macos-create-app-bundle --standalone") elif sys.platform == "win32": - expected_run_cmd += f" --windows-icon-from-ico={str(self.win_icon)}" + expected_run_cmd += f" --windows-icon-from-ico={str(self.win_icon)} --onefile" config_file = self.temp_example_webenginequick / "pysidedeploy.spec" @@ -307,6 +387,13 @@ class TestPySide6DeployWebEngine(DeployTestBase): config_obj.get_value("qt", "excluded_qml_plugins"), "QtCharts,QtQuick3D,QtSensors,QtTest", ) + expected_modules = {"Core", "Gui", "Quick", "Qml", "WebEngineQuick", "Network", "OpenGL", + "Positioning", "WebEngineCore", "WebChannel", "WebChannelQuick", + "QmlModels"} + if sys.platform != "win32": + expected_modules.add("DBus") + obtained_modules = set(config_obj.get_value("qt", "modules").split(",")) + self.assertEqual(obtained_modules, expected_modules) if __name__ == "__main__": diff --git a/sources/shiboken6/.cmake.conf b/sources/shiboken6/.cmake.conf index 836ed305..7a532df2 100644 --- a/sources/shiboken6/.cmake.conf +++ b/sources/shiboken6/.cmake.conf @@ -1,5 +1,5 @@ set(shiboken_MAJOR_VERSION "6") -set(shiboken_MINOR_VERSION "6") +set(shiboken_MINOR_VERSION "7") set(shiboken_MICRO_VERSION "2") set(shiboken_PRE_RELEASE_VERSION_TYPE "") set(shiboken_PRE_RELEASE_VERSION "") diff --git a/sources/shiboken6/ApiExtractor/CMakeLists.txt b/sources/shiboken6/ApiExtractor/CMakeLists.txt index f80be6de..7aa2fbd1 100644 --- a/sources/shiboken6/ApiExtractor/CMakeLists.txt +++ b/sources/shiboken6/ApiExtractor/CMakeLists.txt @@ -18,6 +18,7 @@ abstractmetafunction.cpp abstractmetafunction.h abstractmetalang.cpp abstractmetalang.h abstractmetalang_helpers.h abstractmetalang_typedefs.h abstractmetatype.cpp abstractmetatype.h addedfunction.cpp addedfunction.h addedfunction_p.h +anystringview_helpers.cpp anystringview_helpers.h apiextractor.cpp apiextractor.h apiextractorflags.h apiextractorresult.cpp apiextractorresult.h arraytypeentry.h diff --git a/sources/shiboken6/ApiExtractor/abstractmetabuilder.cpp b/sources/shiboken6/ApiExtractor/abstractmetabuilder.cpp index e922f8d5..0720fca7 100644 --- a/sources/shiboken6/ApiExtractor/abstractmetabuilder.cpp +++ b/sources/shiboken6/ApiExtractor/abstractmetabuilder.cpp @@ -161,6 +161,11 @@ const QHash &AbstractMetaBuilder::typeEntryToEn return d->m_enums; } +const QMultiHash &AbstractMetaBuilder::typedefTargetToName() const +{ + return d->m_typedefTargetToName; +} + void AbstractMetaBuilderPrivate::checkFunctionModifications() const { const auto &entries = TypeDatabase::instance()->entries(); @@ -245,7 +250,7 @@ void AbstractMetaBuilderPrivate::registerHashFunction(const FunctionModelItem &f void AbstractMetaBuilderPrivate::registerToStringCapabilityIn(const NamespaceModelItem &nsItem) { - const FunctionList &streamOps = nsItem->findFunctions(u"operator<<"_s); + const FunctionList &streamOps = nsItem->findFunctions("operator<<"); for (const FunctionModelItem &item : streamOps) registerToStringCapability(item, nullptr); for (const NamespaceModelItem &ni : nsItem->namespaces()) @@ -381,7 +386,6 @@ bool AbstractMetaBuilderPrivate::traverseStreamOperator(const FunctionModelItem streamFunction->setArguments(arguments); - *streamFunction += AbstractMetaFunction::FinalInTargetLang; streamFunction->setAccess(Access::Public); AbstractMetaClassPtr funcClass; @@ -422,8 +426,8 @@ void AbstractMetaBuilderPrivate::sortLists() // this is a temporary solution before new type revision implementation // We need move QMetaObject register before QObject. Dependencies additionalDependencies; - if (auto qObjectClass = AbstractMetaClass::findClass(m_metaClasses, u"QObject")) { - if (auto qMetaObjectClass = AbstractMetaClass::findClass(m_metaClasses, u"QMetaObject")) { + if (auto qObjectClass = AbstractMetaClass::findClass(m_metaClasses, "QObject")) { + if (auto qMetaObjectClass = AbstractMetaClass::findClass(m_metaClasses, "QMetaObject")) { Dependency dependency; dependency.parent = qMetaObjectClass; dependency.child = qObjectClass; @@ -649,7 +653,7 @@ void AbstractMetaBuilderPrivate::traverseDom(const FileModelItem &dom, } { - const FunctionList &hashFunctions = dom->findFunctions(u"qHash"_s); + const FunctionList &hashFunctions = dom->findFunctions("qHash"); for (const FunctionModelItem &item : hashFunctions) registerHashFunction(item, nullptr); } @@ -963,9 +967,31 @@ std::optional return metaEnum; } -AbstractMetaClassPtr AbstractMetaBuilderPrivate::traverseTypeDef(const FileModelItem &, - const TypeDefModelItem &typeDef, - const AbstractMetaClassPtr ¤tClass) +AbstractMetaClassPtr + AbstractMetaBuilderPrivate::traverseTypeDef(const FileModelItem &dom, + const TypeDefModelItem &typeDef, + const AbstractMetaClassPtr ¤tClass) +{ + auto result = traverseTypeDefHelper(dom, typeDef, currentClass); + if (!result && typeDef->type().isPlain()) { + const auto &type = typeDef->type(); + QString fullName; + if (currentClass) + fullName += currentClass->qualifiedCppName() + "::"_L1; + fullName += typeDef->name(); + QString targetName = typeDef->type().toString(); + m_typedefTargetToName.insert(targetName, fullName); + const QByteArray normalized = QMetaObject::normalizedType(targetName.toUtf8().constData()); + if (targetName != QLatin1StringView(normalized)) + m_typedefTargetToName.insert(QString::fromUtf8(normalized), fullName); + } + return result; +} + +AbstractMetaClassPtr + AbstractMetaBuilderPrivate::traverseTypeDefHelper(const FileModelItem &, + const TypeDefModelItem &typeDef, + const AbstractMetaClassPtr ¤tClass) { TypeDatabase *types = TypeDatabase::instance(); QString className = stripTemplateArgs(typeDef->name()); @@ -1234,7 +1260,7 @@ void AbstractMetaBuilderPrivate::traverseNamespaceMembers(const NamespaceModelIt static inline QString fieldSignatureWithType(const VariableModelItem &field) { - return field->name() + QStringLiteral(" -> ") + field->type().toString(); + return field->name() + " -> "_L1 + field->type().toString(); } static inline QString qualifiedFieldSignatureWithType(const QString &className, @@ -1328,7 +1354,7 @@ void AbstractMetaBuilderPrivate::fixReturnTypeOfConversionOperator(AbstractMetaF return; TypeDatabase *types = TypeDatabase::instance(); - static const QRegularExpression operatorRegExp(QStringLiteral("^operator ")); + static const QRegularExpression operatorRegExp("^operator "_L1); Q_ASSERT(operatorRegExp.isValid()); QString castTo = metaFunction->name().remove(operatorRegExp).trimmed(); @@ -1384,7 +1410,7 @@ void AbstractMetaBuilderPrivate::traverseFunctions(const ScopeModelItem& scopeIt for (AbstractMetaFunction *metaFunction : functions) { if (metaClass->isNamespace()) - *metaFunction += AbstractMetaFunction::Static; + metaFunction->setCppAttribute(FunctionAttribute::Static); const auto propertyFunction = metaClass->searchPropertyFunction(metaFunction->name()); if (propertyFunction.index >= 0) { @@ -1425,18 +1451,12 @@ void AbstractMetaBuilderPrivate::traverseFunctions(const ScopeModelItem& scopeIt } } - const bool isInvalidDestructor = metaFunction->isDestructor() && metaFunction->isPrivate(); - const bool isInvalidConstructor = metaFunction->functionType() == AbstractMetaFunction::ConstructorFunction - && metaFunction->isPrivate(); - if (isInvalidConstructor) + if (metaFunction->functionType() == AbstractMetaFunction::ConstructorFunction + && metaFunction->isPrivate()) { metaClass->setHasPrivateConstructor(true); - if ((isInvalidDestructor || isInvalidConstructor) - && !metaClass->hasNonPrivateConstructor()) { - *metaClass += AbstractMetaClass::FinalInTargetLang; - } else if (metaFunction->isConstructor() && !metaFunction->isPrivate()) { - *metaClass -= AbstractMetaClass::FinalInTargetLang; - metaClass->setHasNonPrivateConstructor(true); } + if (metaFunction->isConstructor() && !metaFunction->isPrivate()) // Including Copy CT + metaClass->setHasNonPrivateConstructor(true); if (!metaFunction->isDestructor() && !(metaFunction->isPrivate() && metaFunction->functionType() == AbstractMetaFunction::ConstructorFunction)) { @@ -1527,22 +1547,13 @@ void AbstractMetaBuilderPrivate::applyFunctionModifications(AbstractMetaFunction func->setOriginalName(func->name()); func->setName(mod.renamedToName()); } else if (mod.isAccessModifier()) { - funcRef -= AbstractMetaFunction::Friendly; - if (mod.isPublic()) funcRef.modifyAccess(Access::Public); else if (mod.isProtected()) funcRef.modifyAccess(Access::Protected); else if (mod.isPrivate()) funcRef.modifyAccess(Access::Private); - else if (mod.isFriendly()) - funcRef += AbstractMetaFunction::Friendly; } - - if (mod.isFinal()) - funcRef += AbstractMetaFunction::FinalInTargetLang; - else if (mod.isNonFinal()) - funcRef -= AbstractMetaFunction::FinalInTargetLang; } } @@ -1563,7 +1574,8 @@ bool AbstractMetaBuilderPrivate::setupInheritance(const AbstractMetaClassPtr &me &info, &baseContainerType); if (templ) { setupInheritance(templ); - inheritTemplate(metaClass, templ, info); + if (!inheritTemplate(metaClass, templ, info)) + return false; metaClass->typeEntry()->setBaseContainerType(templ->typeEntry()); return true; } @@ -1580,9 +1592,9 @@ bool AbstractMetaBuilderPrivate::setupInheritance(const AbstractMetaClassPtr &me return true; } - qCWarning(lcShiboken).noquote().nospace() - << QStringLiteral("template baseclass '%1' of '%2' is not known") - .arg(baseClasses.constFirst(), metaClass->name()); + qCWarning(lcShiboken, "template baseclass '%s' of '%s' is not known", + qPrintable(baseClasses.constFirst()), + qPrintable(metaClass->name())); return false; } @@ -1770,7 +1782,7 @@ bool AbstractMetaBuilderPrivate::traverseAddedMemberFunction(const AddedFunction const AbstractMetaArgumentList fargs = metaFunction->arguments(); if (metaClass->isNamespace()) - *metaFunction += AbstractMetaFunction::Static; + metaFunction->setCppAttribute(FunctionAttribute::Static); if (metaFunction->name() == metaClass->name()) { metaFunction->setFunctionType(AbstractMetaFunction::ConstructorFunction); if (fargs.size() == 1) { @@ -2035,7 +2047,8 @@ AbstractMetaFunction *AbstractMetaBuilderPrivate::traverseFunction(const Functio if (functionItem->isFriend()) return nullptr; - const bool deprecated = functionItem->isDeprecated(); + const auto cppAttributes = functionItem->attributes(); + const bool deprecated = cppAttributes.testFlag(FunctionAttribute::Deprecated); if (deprecated && m_skipDeprecated) { rejectFunction(functionItem, currentClass, AbstractMetaBuilder::GenerationDisabled, u" is deprecated."_s); @@ -2044,6 +2057,7 @@ AbstractMetaFunction *AbstractMetaBuilderPrivate::traverseFunction(const Functio AbstractMetaFunction::Flags flags; auto *metaFunction = new AbstractMetaFunction(functionName); + metaFunction->setCppAttributes(cppAttributes); const QByteArray cSignature = signature.toUtf8(); const QString unresolvedSignature = QString::fromUtf8(QMetaObject::normalizedSignature(cSignature.constData())); @@ -2051,35 +2065,12 @@ AbstractMetaFunction *AbstractMetaBuilderPrivate::traverseFunction(const Functio if (functionItem->isHiddenFriend()) flags.setFlag(AbstractMetaFunction::Flag::HiddenFriend); metaFunction->setSourceLocation(functionItem->sourceLocation()); - if (deprecated) - *metaFunction += AbstractMetaFunction::Deprecated; // Additional check for assignment/move assignment down below metaFunction->setFunctionType(functionTypeFromCodeModel(functionItem->functionType())); metaFunction->setConstant(functionItem->isConstant()); metaFunction->setExceptionSpecification(functionItem->exceptionSpecification()); - if (functionItem->isAbstract()) - *metaFunction += AbstractMetaFunction::Abstract; - - if (functionItem->isVirtual()) { - *metaFunction += AbstractMetaFunction::VirtualCppMethod; - if (functionItem->isOverride()) - *metaFunction += AbstractMetaFunction::OverriddenCppMethod; - if (functionItem->isFinal()) - *metaFunction += AbstractMetaFunction::FinalCppMethod; - } else { - *metaFunction += AbstractMetaFunction::FinalInTargetLang; - } - - if (functionItem->isInvokable()) - *metaFunction += AbstractMetaFunction::Invokable; - - if (functionItem->isStatic()) { - *metaFunction += AbstractMetaFunction::Static; - *metaFunction += AbstractMetaFunction::FinalInTargetLang; - } - // Access rights metaFunction->setAccess(functionItem->accessPolicy()); @@ -2089,7 +2080,6 @@ AbstractMetaFunction *AbstractMetaBuilderPrivate::traverseFunction(const Functio metaFunction->setType(AbstractMetaType::createVoid()); break; case AbstractMetaFunction::ConstructorFunction: - metaFunction->setExplicit(functionItem->isExplicit()); metaFunction->setName(currentClass->name()); metaFunction->setType(AbstractMetaType::createVoid()); break; @@ -2158,7 +2148,8 @@ AbstractMetaFunction *AbstractMetaBuilderPrivate::traverseFunction(const Functio // If an invalid argument has a default value, simply remove it // unless the function is virtual (since the override in the // wrapper can then not correctly be generated). - if (arg->defaultValue() && !functionItem->isVirtual()) { + if (arg->defaultValue() + && !functionItem->attributes().testFlag(FunctionAttribute::Virtual)) { if (!currentClass || currentClass->typeEntry()->generateCode()) { const QString signature = qualifiedFunctionSignatureWithType(functionItem, className); qCWarning(lcShiboken, "%s", @@ -2379,7 +2370,7 @@ TypeEntryCList AbstractMetaBuilderPrivate::findTypeEntries(const QString &qualif AbstractMetaClassCPtr AbstractMetaBuilderPrivate::resolveTypeSystemTypeDef(const AbstractMetaType &t) const { if (t.hasInstantiations()) { - auto pred = [t](const TypeClassEntry &e) { return e.type.equals(t); }; + auto pred = [t](const TypeClassEntry &e) { return e.type == t; }; auto it = std::find_if(m_typeSystemTypeDefs.cbegin(), m_typeSystemTypeDefs.cend(), pred); if (it != m_typeSystemTypeDefs.cend()) return it->klass; @@ -2650,7 +2641,7 @@ std::optional bool isConstCharStarCase = oneDimensionalArrayOfUnspecifiedSize && typeInfo.qualifiedName().size() == 1 - && typeInfo.qualifiedName().at(0) == QStringLiteral("char") + && typeInfo.qualifiedName().at(0) == "char"_L1 && typeInfo.indirections() == 0 && typeInfo.isConstant() && typeInfo.referenceType() == NoReference @@ -2863,7 +2854,7 @@ qint64 AbstractMetaBuilderPrivate::findOutValueFromString(const QString &stringV // This is a very lame way to handle expression evaluation, // but it is not critical and will do for the time being. - static const QRegularExpression variableNameRegExp(QStringLiteral("^[a-zA-Z_][a-zA-Z0-9_]*$")); + static const QRegularExpression variableNameRegExp("^[a-zA-Z_][a-zA-Z0-9_]*$"_L1); Q_ASSERT(variableNameRegExp.isValid()); if (!variableNameRegExp.match(stringValue).hasMatch()) { ok = true; @@ -3138,80 +3129,103 @@ std::optional AbstractMetaClassPtr AbstractMetaBuilder::inheritTemplateClass(const ComplexTypeEntryPtr &te, const AbstractMetaClassCPtr &templateClass, - const AbstractMetaTypeList &templateTypes, - InheritTemplateFlags flags) + const AbstractMetaTypeList &templateTypes) { auto result = std::make_shared(); result->setTypeDef(true); result->setTypeEntry(te); if (!AbstractMetaBuilderPrivate::inheritTemplate(result, templateClass, - templateTypes, flags)) { + templateTypes)) { return {}; } AbstractMetaBuilderPrivate::inheritTemplateFunctions(result); return result; } + +static std::optional + inheritTemplateParameter(const AbstractMetaClassPtr &subclass, + const AbstractMetaClassCPtr &templateClass, + const TypeInfo &info, QString *errorMessage) +{ + QString typeName = info.qualifiedName().join("::"_L1); + TypeDatabase *typeDb = TypeDatabase::instance(); + TypeEntryPtr t; + // Check for a non-type template integer parameter, that is, for a base + // "template Matrix" and subclass + // "typedef Matrix<2,3> Matrix2x3;". If so, create dummy entries of + // EnumValueTypeEntry for the integer values encountered on the fly. + if (isNumber(typeName)) { + t = typeDb->findType(typeName); + if (!t) { + auto parent = typeSystemTypeEntry(subclass->typeEntry()); + t = TypeDatabase::instance()->addConstantValueTypeEntry(typeName, parent); + } + } else { + QStringList possibleNames; + possibleNames << subclass->qualifiedCppName() + "::"_L1 + typeName; + possibleNames << templateClass->qualifiedCppName() + "::"_L1 + typeName; + if (subclass->enclosingClass()) + possibleNames << subclass->enclosingClass()->qualifiedCppName() + "::"_L1 + typeName; + possibleNames << typeName; + + for (const QString &possibleName : std::as_const(possibleNames)) { + t = typeDb->findType(possibleName); + if (t) + break; + } + } + + if (!t) { + *errorMessage = msgIgnoringTemplateParameter(typeName, + "The corresponding type was not found in the typesystem."); + return std::nullopt; + } + + if (t->isContainer()) { + *errorMessage = msgIgnoringTemplateParameter(typeName, + "Template inheritance from nested containers is not supported"); + return std::nullopt; + } + AbstractMetaType result(t); + result.setConstant(info.isConstant()); + result.setReferenceType(info.referenceType()); + result.setIndirectionsV(info.indirectionsV()); + result.decideUsagePattern(); + return result; +} + bool AbstractMetaBuilderPrivate::inheritTemplate(const AbstractMetaClassPtr &subclass, const AbstractMetaClassCPtr &templateClass, const TypeInfo &info) { AbstractMetaTypeList templateTypes; + QString errorMessage; for (const TypeInfo &i : info.instantiations()) { - QString typeName = i.qualifiedName().join(u"::"_s); - TypeDatabase *typeDb = TypeDatabase::instance(); - TypeEntryPtr t; - // Check for a non-type template integer parameter, that is, for a base - // "template Matrix" and subclass - // "typedef Matrix<2,3> Matrix2x3;". If so, create dummy entries of - // EnumValueTypeEntry for the integer values encountered on the fly. - if (isNumber(typeName)) { - t = typeDb->findType(typeName); - if (!t) { - auto parent = typeSystemTypeEntry(subclass->typeEntry()); - t = TypeDatabase::instance()->addConstantValueTypeEntry(typeName, parent); - } - } else { - QStringList possibleNames; - possibleNames << subclass->qualifiedCppName() + u"::"_s + typeName; - possibleNames << templateClass->qualifiedCppName() + u"::"_s + typeName; - if (subclass->enclosingClass()) - possibleNames << subclass->enclosingClass()->qualifiedCppName() + u"::"_s + typeName; - possibleNames << typeName; - - for (const QString &possibleName : std::as_const(possibleNames)) { - t = typeDb->findType(possibleName); - if (t) - break; - } - } - - if (t) { - AbstractMetaType temporaryType(t); - temporaryType.setConstant(i.isConstant()); - temporaryType.setReferenceType(i.referenceType()); - temporaryType.setIndirectionsV(i.indirectionsV()); - temporaryType.decideUsagePattern(); - templateTypes << temporaryType; + const auto typeO = inheritTemplateParameter(subclass, templateClass, i, &errorMessage); + if (typeO.has_value()) { + templateTypes.append(typeO.value()); } else { - qCWarning(lcShiboken).noquote().nospace() - << "Ignoring template parameter " << typeName << " from " - << info.toString() << ". The corresponding type was not found in the typesystem."; + errorMessage = msgInheritTemplateIssue(subclass, info, errorMessage); + qCWarning(lcShiboken, "%s", qPrintable(errorMessage)); } } + if (templateTypes.isEmpty()) { + errorMessage = msgInheritTemplateIssue(subclass, info, + "No template parameters could be inherited"_L1); + qCWarning(lcShiboken, "%s", qPrintable(errorMessage)); + return false; + } return inheritTemplate(subclass, templateClass, templateTypes); } bool AbstractMetaBuilderPrivate::inheritTemplate(const AbstractMetaClassPtr &subclass, const AbstractMetaClassCPtr &templateClass, - const AbstractMetaTypeList &templateTypes, - InheritTemplateFlags flags) + const AbstractMetaTypeList &templateTypes) { subclass->setTemplateBaseClass(templateClass); - if (flags.testFlag(InheritTemplateFlag::SetEnclosingClass)) - subclass->setEnclosingClass(templateClass->enclosingClass()); subclass->setTemplateBaseClassInstantiations(templateTypes); subclass->setBaseClass(templateClass->baseClass()); return true; @@ -3550,16 +3564,22 @@ static QList > if (!result.isValid() && graph.nodeCount()) { QTemporaryFile tempFile(QDir::tempPath() + u"/cyclic_depXXXXXX.dot"_s); tempFile.setAutoRemove(false); - tempFile.open(); - graph.dumpDot(tempFile.fileName(), - [] (const AbstractMetaClassCPtr &c) { return c->name(); }); + const bool ok = tempFile.open(); + if (ok) { + graph.dumpDot(tempFile.fileName(), + [] (const AbstractMetaClassCPtr &c) { return c->name(); }); + } QString message; QTextStream str(&message); str << "Cyclic dependency of classes found:"; for (const auto &c : result.cyclic) str << ' ' << c->name(); - str << ". Graph can be found at \"" << QDir::toNativeSeparators(tempFile.fileName()) << '"'; + str << '.'; + if (ok) { + str << " Graph can be found at \"" + << QDir::toNativeSeparators(tempFile.fileName()) << '"'; + } qCWarning(lcShiboken, "%s", qPrintable(message)); } diff --git a/sources/shiboken6/ApiExtractor/abstractmetabuilder.h b/sources/shiboken6/ApiExtractor/abstractmetabuilder.h index b00a6936..20261ed3 100644 --- a/sources/shiboken6/ApiExtractor/abstractmetabuilder.h +++ b/sources/shiboken6/ApiExtractor/abstractmetabuilder.h @@ -54,6 +54,7 @@ public: const AbstractMetaFunctionCList &globalFunctions() const; const AbstractMetaEnumList &globalEnums() const; const QHash &typeEntryToEnumsHash() const; + const QMultiHash &typedefTargetToName() const; bool build(const QByteArrayList &arguments, ApiExtractorFlags apiExtractorFlags = {}, @@ -99,10 +100,9 @@ public: const AbstractMetaTypeList &templateTypes); static AbstractMetaClassPtr - inheritTemplateClass(const ComplexTypeEntryPtr &te, - const AbstractMetaClassCPtr &templateClass, - const AbstractMetaTypeList &templateTypes, - InheritTemplateFlags flags = {}); + inheritTemplateClass(const ComplexTypeEntryPtr &te, + const AbstractMetaClassCPtr &templateClass, + const AbstractMetaTypeList &templateTypes); /// Performs a template specialization of the member function. /// \param function Member function diff --git a/sources/shiboken6/ApiExtractor/abstractmetabuilder_p.h b/sources/shiboken6/ApiExtractor/abstractmetabuilder_p.h index 39ade643..e65a4f17 100644 --- a/sources/shiboken6/ApiExtractor/abstractmetabuilder_p.h +++ b/sources/shiboken6/ApiExtractor/abstractmetabuilder_p.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -77,6 +78,9 @@ public: AbstractMetaClassPtr traverseTypeDef(const FileModelItem &dom, const TypeDefModelItem &typeDef, const AbstractMetaClassPtr ¤tClass); + AbstractMetaClassPtr traverseTypeDefHelper(const FileModelItem &dom, + const TypeDefModelItem &typeDef, + const AbstractMetaClassPtr ¤tClass); void traverseTypesystemTypedefs(); AbstractMetaClassPtr traverseClass(const FileModelItem &dom, const ClassModelItem &item, @@ -186,8 +190,7 @@ public: const TypeInfo &info); static bool inheritTemplate(const AbstractMetaClassPtr &subclass, const AbstractMetaClassCPtr &templateClass, - const AbstractMetaTypeList &templateTypes, - InheritTemplateFlags flags = {}); + const AbstractMetaTypeList &templateTypes); static AbstractMetaFunctionPtr inheritTemplateFunction(const AbstractMetaFunctionCPtr &function, @@ -240,6 +243,7 @@ public: QFileInfoList m_globalHeaders; QStringList m_headerPaths; mutable QHash m_resolveIncludeHash; + QMultiHash m_typedefTargetToName; QList m_typeSystemTypeDefs; // look up metatype->class for type system typedefs ApiExtractorFlags m_apiExtractorFlags; bool m_skipDeprecated = false; diff --git a/sources/shiboken6/ApiExtractor/abstractmetafunction.cpp b/sources/shiboken6/ApiExtractor/abstractmetafunction.cpp index 6bcf5ea9..11a02f15 100644 --- a/sources/shiboken6/ApiExtractor/abstractmetafunction.cpp +++ b/sources/shiboken6/ApiExtractor/abstractmetafunction.cpp @@ -48,7 +48,6 @@ public: AbstractMetaFunctionPrivate() : m_constant(false), m_reverse(false), - m_explicit(false), m_pointerOperator(false), m_isCallOperator(false) { @@ -87,10 +86,10 @@ public: AddedFunctionPtr m_addedFunction; SourceLocation m_sourceLocation; AbstractMetaFunction::Attributes m_attributes; + FunctionAttributes m_cppAttributes; AbstractMetaFunction::Flags m_flags; uint m_constant : 1; uint m_reverse : 1; - uint m_explicit : 1; uint m_pointerOperator : 1; uint m_isCallOperator : 1; mutable int m_cachedOverloadNumber = TypeSystem::OverloadNumberUnset; @@ -120,9 +119,9 @@ AbstractMetaFunction::AbstractMetaFunction(const AddedFunctionPtr &addedFunc) : setAccess(Access::Public); break; } - AbstractMetaFunction::Attributes atts = AbstractMetaFunction::FinalInTargetLang; + AbstractMetaFunction::Attributes atts; if (addedFunc->isStatic()) - atts |= AbstractMetaFunction::Static; + setCppAttribute(FunctionAttribute::Static); if (addedFunc->isClassMethod()) atts |= AbstractMetaFunction::ClassMethod; setAttributes(atts); @@ -215,12 +214,12 @@ void AbstractMetaFunction::setPointerOperator(bool value) bool AbstractMetaFunction::isExplicit() const { - return d->m_explicit; + return d->m_cppAttributes.testFlag(FunctionAttribute::Explicit); } void AbstractMetaFunction::setExplicit(bool isExplicit) { - d->m_explicit = isExplicit; + d->m_cppAttributes.setFlag(FunctionAttribute::Explicit, isExplicit); } bool AbstractMetaFunction::returnsBool() const @@ -262,6 +261,21 @@ void AbstractMetaFunction::operator-=(AbstractMetaFunction::Attribute attribute) d->m_attributes.setFlag(attribute, false); } +FunctionAttributes AbstractMetaFunction::cppAttributes() const +{ + return d->m_cppAttributes; +} + +void AbstractMetaFunction::setCppAttributes(FunctionAttributes a) +{ + d->m_cppAttributes = a; +} + +void AbstractMetaFunction::setCppAttribute(FunctionAttribute a, bool on) +{ + d->m_cppAttributes.setFlag(a, on); +} + AbstractMetaFunction::Flags AbstractMetaFunction::flags() const { return d->m_flags; @@ -342,7 +356,7 @@ AbstractMetaFunction::CompareResult AbstractMetaFunction::compareTo(const Abstra result |= EqualImplementor; // Attributes - if (attributes() == other->attributes()) + if (attributes() == other->attributes() && cppAttributes() == other->cppAttributes()) result |= EqualAttributes; // Compare types @@ -427,6 +441,10 @@ AbstractMetaFunction *AbstractMetaFunction::copy() const { auto *cpy = new AbstractMetaFunction; cpy->setAttributes(attributes()); + auto ca = cppAttributes(); + // Historical bug: explicit was not copied! (causing nontypetemplate_test.py fail) + ca.setFlag(FunctionAttribute::Explicit, false); + cpy->setCppAttributes(ca); cpy->setFlags(flags()); cpy->setAccess(access()); cpy->setName(name()); @@ -574,6 +592,11 @@ bool AbstractMetaFunction::isUserAdded() const return d->m_addedFunction && !d->m_addedFunction->isDeclaration(); } +bool AbstractMetaFunction::isUserAddedPythonOverride() const +{ + return d->m_addedFunction && d->m_addedFunction->isPythonOverride(); +} + bool AbstractMetaFunction::isUserDeclared() const { return d->m_addedFunction && d->m_addedFunction->isDeclaration(); @@ -729,7 +752,8 @@ static bool modifiedUndeprecated(const FunctionModification &mod) bool AbstractMetaFunction::isDeprecated() const { const auto &mods = modifications(declaringClass()); - return d->m_attributes.testFlag(Attribute::Deprecated) + + return d->m_cppAttributes.testFlag(FunctionAttribute::Deprecated) ? std::none_of(mods.cbegin(), mods.cend(), modifiedUndeprecated) : std::any_of(mods.cbegin(), mods.cend(), modifiedDeprecated); } @@ -1020,9 +1044,10 @@ QString AbstractMetaFunction::signatureComment() const QString AbstractMetaFunction::debugSignature() const { QString result; - const bool isOverride = attributes() & AbstractMetaFunction::OverriddenCppMethod; - const bool isFinal = attributes() & AbstractMetaFunction::FinalCppMethod; - if (!isOverride && !isFinal && (attributes() & AbstractMetaFunction::VirtualCppMethod)) + const auto attributes = cppAttributes(); + const bool isOverride = attributes.testFlag(FunctionAttribute::Override); + const bool isFinal = attributes.testFlag(FunctionAttribute::Final); + if (!isOverride && !isFinal && (attributes.testFlag(FunctionAttribute::Virtual))) result += u"virtual "_s; if (d->m_implementingClass) result += d->m_implementingClass->qualifiedCppName() + u"::"_s; @@ -1120,6 +1145,17 @@ void AbstractMetaFunction::setTypeEntry(const FunctionTypeEntryPtr &typeEntry) d->m_typeEntry = typeEntry; } +QString AbstractMetaFunction::targetLangPackage() const +{ + if (d->m_addedFunction != nullptr) + return d->m_addedFunction->targetLangPackage(); + if (d->m_class != nullptr) + return d->m_class->typeEntry()->targetLangPackage(); + if (d->m_typeEntry != nullptr) + return d->m_typeEntry->targetLangPackage(); + return {}; +} + bool AbstractMetaFunction::isCallOperator() const { return d->m_name == u"operator()"; @@ -1375,7 +1411,7 @@ bool AbstractMetaFunction::isInplaceOperator() const bool AbstractMetaFunction::isVirtual() const { - return d->m_attributes.testFlag(AbstractMetaFunction::VirtualCppMethod); + return d->m_cppAttributes.testFlag(FunctionAttribute::Virtual); } QString AbstractMetaFunctionPrivate::modifiedName(const AbstractMetaFunction *q) const @@ -1400,7 +1436,7 @@ QString AbstractMetaFunction::modifiedName() const AbstractMetaFunctionCPtr AbstractMetaFunction::find(const AbstractMetaFunctionCList &haystack, - QStringView needle) + QAnyStringView needle) { for (const auto &f : haystack) { if (f->name() == needle) @@ -1519,7 +1555,7 @@ bool AbstractMetaFunction::injectedCodeUsesPySelf() const bool AbstractMetaFunction::injectedCodeCallsPythonOverride() const { static const QRegularExpression - overrideCallRegexCheck(QStringLiteral(R"(PyObject_Call\s*\(\s*%PYTHON_METHOD_OVERRIDE\s*,)")); + overrideCallRegexCheck(R"(PyObject_Call\s*\(\s*%PYTHON_METHOD_OVERRIDE\s*,)"_L1); Q_ASSERT(overrideCallRegexCheck.isValid()); return injectedCodeContains(overrideCallRegexCheck, TypeSystem::CodeSnipPositionAny, TypeSystem::NativeCode); @@ -1529,13 +1565,13 @@ bool AbstractMetaFunction::injectedCodeHasReturnValueAttribution(TypeSystem::Lan { if (language == TypeSystem::TargetLangCode) { static const QRegularExpression - retValAttributionRegexCheck_target(QStringLiteral(R"(%PYARG_0\s*=[^=]\s*.+)")); + retValAttributionRegexCheck_target(R"(%PYARG_0\s*=[^=]\s*.+)"_L1); Q_ASSERT(retValAttributionRegexCheck_target.isValid()); return injectedCodeContains(retValAttributionRegexCheck_target, TypeSystem::CodeSnipPositionAny, language); } static const QRegularExpression - retValAttributionRegexCheck_native(QStringLiteral(R"(%0\s*=[^=]\s*.+)")); + retValAttributionRegexCheck_native(R"(%0\s*=[^=]\s*.+)"_L1); Q_ASSERT(retValAttributionRegexCheck_native.isValid()); return injectedCodeContains(retValAttributionRegexCheck_native, TypeSystem::CodeSnipPositionAny, language); } @@ -1633,9 +1669,9 @@ void AbstractMetaFunction::formatDebugVerbose(QDebug &debug) const debug << " [userAdded]"; if (isUserDeclared()) debug << " [userDeclared]"; - if (d->m_explicit) + if (d->m_cppAttributes.testFlag(FunctionAttribute::Explicit)) debug << " [explicit]"; - if (attributes().testFlag(AbstractMetaFunction::Deprecated)) + if (d->m_cppAttributes.testFlag(FunctionAttribute::Deprecated)) debug << " [deprecated]"; if (d->m_pointerOperator) debug << " [operator->]"; diff --git a/sources/shiboken6/ApiExtractor/abstractmetafunction.h b/sources/shiboken6/ApiExtractor/abstractmetafunction.h index 1966ff33..e252e439 100644 --- a/sources/shiboken6/ApiExtractor/abstractmetafunction.h +++ b/sources/shiboken6/ApiExtractor/abstractmetafunction.h @@ -92,14 +92,8 @@ public: enum Attribute { None = 0x00000000, - Friendly = 0x00000001, - - Abstract = 0x00000002, - Static = 0x00000004, ClassMethod = 0x00000008, - FinalInTargetLang = 0x00000010, - GetterFunction = 0x00000020, SetterFunction = 0x00000040, @@ -108,14 +102,8 @@ public: PropertyResetter = 0x00000400, PropertyNotify = 0x00000800, - Invokable = 0x00001000, - - VirtualCppMethod = 0x00010000, - OverriddenCppMethod = 0x00020000, - FinalCppMethod = 0x00040000, // Add by meta builder (implicit constructors, inherited methods, etc) AddedMethod = 0x001000000, - Deprecated = 0x002000000 // Code annotation }; Q_DECLARE_FLAGS(Attributes, Attribute) Q_FLAG(Attribute) @@ -126,6 +114,10 @@ public: void operator+=(Attribute attribute); void operator-=(Attribute attribute); + FunctionAttributes cppAttributes() const; + void setCppAttributes(FunctionAttributes a); + void setCppAttribute(FunctionAttribute a, bool on = true); + enum class Flag { // Internal flags not relevant for comparing functions // Binary operator whose leading/trailing argument was removed by metabuilder OperatorLeadingClassArgumentRemoved = 0x1, @@ -140,11 +132,9 @@ public: Flags flags() const; void setFlags(Flags f); - bool isFinalInTargetLang() const; bool isAbstract() const; bool isClassMethod() const; bool isStatic() const; - bool isInvokable() const; bool isPropertyReader() const; bool isPropertyWriter() const; bool isPropertyResetter() const; @@ -310,6 +300,7 @@ public: /// Returns true if the AbstractMetaFunction was added by the user via the type system description. bool isUserAdded() const; + bool isUserAddedPythonOverride() const; /// Returns true if the AbstractMetaFunction was declared by the user via /// the type system description. bool isUserDeclared() const; @@ -387,13 +378,14 @@ public: void setPropertySpecIndex(int i); FunctionTypeEntryPtr typeEntry() const; - void setTypeEntry(const FunctionTypeEntryPtr &typeEntry); + QString targetLangPackage() const; + bool isCallOperator() const; static AbstractMetaFunctionCPtr - find(const AbstractMetaFunctionCList &haystack, QStringView needle); + find(const AbstractMetaFunctionCList &haystack, QAnyStringView needle); bool matches(OperatorQueryOptions) const; @@ -452,19 +444,14 @@ private: QScopedPointer d; }; -inline bool AbstractMetaFunction::isFinalInTargetLang() const -{ - return attributes().testFlag(FinalInTargetLang); -} - inline bool AbstractMetaFunction::isAbstract() const { - return attributes().testFlag(Abstract); + return cppAttributes().testFlag(FunctionAttribute::Abstract); } inline bool AbstractMetaFunction::isStatic() const { - return attributes().testFlag(Static); + return cppAttributes().testFlag(FunctionAttribute::Static); } inline bool AbstractMetaFunction::isClassMethod() const @@ -472,11 +459,6 @@ inline bool AbstractMetaFunction::isClassMethod() const return attributes().testFlag(ClassMethod); } -inline bool AbstractMetaFunction::isInvokable() const -{ - return attributes().testFlag(Invokable); -} - inline bool AbstractMetaFunction::isPropertyReader() const { return attributes().testFlag(PropertyReader); diff --git a/sources/shiboken6/ApiExtractor/abstractmetalang.cpp b/sources/shiboken6/ApiExtractor/abstractmetalang.cpp index 87e215c8..fb49cc9d 100644 --- a/sources/shiboken6/ApiExtractor/abstractmetalang.cpp +++ b/sources/shiboken6/ApiExtractor/abstractmetalang.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "abstractmetalang.h" +#include "anystringview_helpers.h" #include "abstractmetalang_helpers.h" #include "abstractmetaargument.h" #include "abstractmetaenum.h" @@ -101,6 +102,7 @@ public: AbstractMetaClassCPtr m_templateBaseClass; AbstractMetaFunctionCList m_functions; + AbstractMetaFunctionCList m_userAddedPythonOverrides; AbstractMetaFieldList m_fields; AbstractMetaEnumList m_enums; QList m_propertySpecs; @@ -176,29 +178,16 @@ AbstractMetaFunctionCList AbstractMetaClass::functionsInTargetLang() const FunctionQueryOptions default_flags = FunctionQueryOption::NormalFunctions | FunctionQueryOption::Visible | FunctionQueryOption::NotRemoved; - // Only public functions in final classes - // default_flags |= isFinal() ? WasPublic : 0; - FunctionQueryOptions public_flags; - if (isFinalInTargetLang()) - public_flags |= FunctionQueryOption::WasPublic; - // Constructors AbstractMetaFunctionCList returned = queryFunctions(FunctionQueryOption::AnyConstructor - | default_flags | public_flags); - - // Final functions - returned += queryFunctions(FunctionQueryOption::FinalInTargetLangFunctions - | FunctionQueryOption::NonStaticFunctions - | default_flags | public_flags); + | default_flags); - // Virtual functions - returned += queryFunctions(FunctionQueryOption::VirtualInTargetLangFunctions - | FunctionQueryOption::NonStaticFunctions - | default_flags | public_flags); + returned += queryFunctions(FunctionQueryOption::NonStaticFunctions + | default_flags); // Static functions returned += queryFunctions(FunctionQueryOption::StaticFunctions - | default_flags | public_flags); + | default_flags); // Empty, private functions, since they aren't caught by the other ones returned += queryFunctions(FunctionQueryOption::Empty | FunctionQueryOption::Invisible); @@ -335,6 +324,11 @@ const AbstractMetaFunctionCList &AbstractMetaClass::functions() const return d->m_functions; } +const AbstractMetaFunctionCList &AbstractMetaClass::userAddedPythonOverrides() const +{ + return d->m_userAddedPythonOverrides; +} + void AbstractMetaClassPrivate::sortFunctions() { std::sort(m_functions.begin(), m_functions.end(), function_sorter); @@ -402,7 +396,13 @@ void AbstractMetaClass::addFunction(const AbstractMetaClassPtr &klass, // to function properly. Such as function modifications nonConstF->setImplementingClass(klass); - klass->d->addFunction(function); + if (function->isUserAddedPythonOverride()) { + nonConstF->setConstant(false); + nonConstF->setCppAttribute(FunctionAttribute::Static); + klass->d->m_userAddedPythonOverrides.append(function); + } else { + klass->d->addFunction(function); + } } bool AbstractMetaClass::hasSignal(const AbstractMetaFunction *other) const @@ -567,12 +567,12 @@ bool AbstractMetaClass::hasFunction(const QString &str) const return bool(findFunction(str)); } -AbstractMetaFunctionCPtr AbstractMetaClass::findFunction(QStringView functionName) const +AbstractMetaFunctionCPtr AbstractMetaClass::findFunction(QAnyStringView functionName) const { return AbstractMetaFunction::find(d->m_functions, functionName); } -AbstractMetaFunctionCList AbstractMetaClass::findFunctions(QStringView functionName) const +AbstractMetaFunctionCList AbstractMetaClass::findFunctions(QAnyStringView functionName) const { AbstractMetaFunctionCList result; std::copy_if(d->m_functions.cbegin(), d->m_functions.cend(), @@ -814,8 +814,7 @@ void AbstractMetaClassPrivate::addConstructor(AbstractMetaFunction::FunctionType auto *f = createFunction(q->name(), t, access, arguments, AbstractMetaType::createVoid(), q); if (access != Access::Private) m_hasNonPrivateConstructor = true; - f->setAttributes(AbstractMetaFunction::FinalInTargetLang - | AbstractMetaFunction::AddedMethod); + f->setAttributes(AbstractMetaFunction::AddedMethod); addFunction(AbstractMetaFunctionCPtr(f)); } @@ -1168,24 +1167,15 @@ bool AbstractMetaClass::queryFunction(const AbstractMetaFunction *f, FunctionQue if (query.testFlag(FunctionQueryOption::Visible) && f->isPrivate()) return false; - if (query.testFlag(FunctionQueryOption::VirtualInTargetLangFunctions) && f->isFinalInTargetLang()) - return false; - if (query.testFlag(FunctionQueryOption::Invisible) && !f->isPrivate()) return false; if (query.testFlag(FunctionQueryOption::Empty) && !f->isEmptyFunction()) return false; - if (query.testFlag(FunctionQueryOption::WasPublic) && !f->wasPublic()) - return false; - if (query.testFlag(FunctionQueryOption::ClassImplements) && f->ownerClass() != f->implementingClass()) return false; - if (query.testFlag(FunctionQueryOption::FinalInTargetLangFunctions) && !f->isFinalInTargetLang()) - return false; - if (query.testFlag(FunctionQueryOption::VirtualInCppFunctions) && !f->isVirtual()) return false; @@ -1474,18 +1464,18 @@ void AbstractMetaClass::fixFunctions(const AbstractMetaClassPtr &klass) } for (const auto &superClassC : d->m_baseClasses) { + for (const auto &pof : superClassC->userAddedPythonOverrides()) { + auto *clonedPof = pof->copy(); + clonedPof->setOwnerClass(klass); + d->m_userAddedPythonOverrides.append(AbstractMetaFunctionCPtr{clonedPof}); + } + auto superClass = std::const_pointer_cast(superClassC); AbstractMetaClass::fixFunctions(superClass); // Since we always traverse the complete hierarchy we are only // interrested in what each super class implements, not what // we may have propagated from their base classes again. AbstractMetaFunctionCList superFuncs; - // Super classes can never be final - if (superClass->isFinalInTargetLang()) { - qCWarning(lcShiboken).noquote().nospace() - << "Final class '" << superClass->name() << "' set to non-final, as it is extended by other classes"; - *superClass -= AbstractMetaClass::FinalInTargetLang; - } superFuncs = superClass->queryFunctions(FunctionQueryOption::ClassImplements); // We are not interested in signals as no bindings are generated for them; // they cause documentation warnings. @@ -1520,29 +1510,11 @@ void AbstractMetaClass::fixFunctions(const AbstractMetaClassPtr &klass) if (cmp & AbstractMetaFunction::EqualArguments) { // Set "override" in case it was not spelled out (since it // is then not detected by clang parsing). - const auto attributes = cf->attributes(); - if (cf->isVirtual() - && !attributes.testFlag(AbstractMetaFunction::OverriddenCppMethod) - && !attributes.testFlag(AbstractMetaFunction::FinalCppMethod)) { - *f += AbstractMetaFunction::OverriddenCppMethod; - } - // Same function, propegate virtual... - if (!(cmp & AbstractMetaFunction::EqualAttributes)) { - if (!f->isEmptyFunction()) { - if (!sf->isFinalInTargetLang() && f->isFinalInTargetLang()) { - *f -= AbstractMetaFunction::FinalInTargetLang; - } -#if 0 - if (!f->isFinalInTargetLang() && f->isPrivate()) { - f->setFunctionType(AbstractMetaFunction::EmptyFunction); - f->setVisibility(AbstractMetaAttributes::Protected); - *f += AbstractMetaAttributes::FinalInTargetLang; - qCWarning(lcShiboken).noquote().nospace() - << QStringLiteral("private virtual function '%1' in '%2'") - .arg(f->signature(), f->implementingClass()->name()); - } -#endif - } + const auto attributes = cf->cppAttributes(); + if (attributes.testFlag(FunctionAttribute::Virtual) + && !attributes.testFlag(FunctionAttribute::Override) + && !attributes.testFlag(FunctionAttribute::Final)) { + f->setCppAttribute(FunctionAttribute::Override); } if (f->access() != sf->access()) { @@ -1559,44 +1531,12 @@ void AbstractMetaClass::fixFunctions(const AbstractMetaClassPtr &klass) // Private overrides of abstract functions have to go into the class or // the subclasses will not compile as non-abstract classes. // But they don't need to be implemented, since they can never be called. - if (f->isPrivate()) { + if (f->isPrivate()) f->setFunctionType(AbstractMetaFunction::EmptyFunction); - *f += AbstractMetaFunction::FinalInTargetLang; - } } // Set the class which first declares this function, afawk f->setDeclaringClass(sf->declaringClass()); - - if (sf->isFinalInTargetLang() && !sf->isPrivate() && !f->isPrivate() && !sf->isStatic() && !f->isStatic()) { - // Shadowed funcion, need to make base class - // function non-virtual - if (f->implementingClass() != sf->implementingClass() - && inheritsFrom(f->implementingClass(), sf->implementingClass())) { - - // Check whether the superclass method has been redefined to non-final - - bool hasNonFinalModifier = false; - bool isBaseImplPrivate = false; - const FunctionModificationList &mods = sf->modifications(sf->implementingClass()); - for (const FunctionModification &mod : mods) { - if (mod.isNonFinal()) { - hasNonFinalModifier = true; - break; - } - if (mod.isPrivate()) { - isBaseImplPrivate = true; - break; - } - } - - if (!hasNonFinalModifier && !isBaseImplPrivate) { - qCWarning(lcShiboken, "%s", - qPrintable(msgShadowingFunction(sf.get(), f.get()))); - } - } - } - } if (cmp & AbstractMetaFunction::EqualDefaultValueOverload) { @@ -1675,6 +1615,7 @@ bool AbstractMetaClass::needsInheritanceSetup() const switch (d->m_typeEntry->type()) { case TypeEntry::NamespaceType: case TypeEntry::SmartPointerType: + case TypeEntry::ContainerType: return false; default: break; @@ -1724,12 +1665,12 @@ std::optional /// Target language base name or complete Target language package.class name. template -static It findClassHelper(It begin, It end, QStringView name) +static It findClassHelper(It begin, It end, QAnyStringView name) { if (name.isEmpty() || begin == end) return end; - if (name.contains(u'.')) { // Search target lang name + if (asv_contains(name,'.')) { // Search target lang name for (auto it = begin; it != end; ++it) { if ((*it)->fullName() == name) return it; @@ -1742,7 +1683,7 @@ static It findClassHelper(It begin, It end, QStringView name) return it; } - if (name.contains(u"::")) // Qualified, cannot possibly match name + if (asv_contains(name, "::")) // Qualified, cannot possibly match name return end; for (auto it = begin; it != end; ++it) { @@ -1754,14 +1695,14 @@ static It findClassHelper(It begin, It end, QStringView name) } AbstractMetaClassPtr AbstractMetaClass::findClass(const AbstractMetaClassList &classes, - QStringView name) + QAnyStringView name) { auto it =findClassHelper(classes.cbegin(), classes.cend(), name); return it != classes.cend() ? *it : nullptr; } AbstractMetaClassCPtr AbstractMetaClass::findClass(const AbstractMetaClassCList &classes, - QStringView name) + QAnyStringView name) { auto it = findClassHelper(classes.cbegin(), classes.cend(), name); return it != classes.cend() ? *it : nullptr; @@ -1800,7 +1741,7 @@ bool inheritsFrom(const AbstractMetaClassCPtr &c, const AbstractMetaClassCPtr &c })); } -bool inheritsFrom(const AbstractMetaClassCPtr &c, const QString &name) +bool inheritsFrom(const AbstractMetaClassCPtr &c, QAnyStringView name) { if (c->qualifiedCppName() == name) return true; @@ -2005,20 +1946,38 @@ QDebug operator<<(QDebug debug, const UsingMember &d) return debug; } -QDebug operator<<(QDebug d, const AbstractMetaClassCPtr &ac) +void formatMetaClass(QDebug &ddebug, const AbstractMetaClass *ac) { - QDebugStateSaver saver(d); - d.noquote(); - d.nospace(); - d << "AbstractMetaClass("; - if (ac) { - ac->format(d); - if (d.verbosity() > 2) - ac->formatMembers(d); + QDebugStateSaver saver(ddebug); + ddebug.noquote(); + ddebug.nospace(); + ddebug << "AbstractMetaClass("; + if (ac != nullptr) { + ac->format(ddebug); + if (ddebug.verbosity() > 2) + ac->formatMembers(ddebug); } else { - d << '0'; + ddebug << '0'; } - d << ')'; + ddebug << ')'; +} + +QDebug operator<<(QDebug d, const AbstractMetaClassCPtr &ac) +{ + formatMetaClass(d, ac.get()); return d; } + +QDebug operator<<(QDebug d, const AbstractMetaClassPtr &ac) +{ + formatMetaClass(d, ac.get()); + return d; +} + +QDebug operator<<(QDebug d, const AbstractMetaClass *ac) +{ + formatMetaClass(d, ac); + return d; +} + #endif // !QT_NO_DEBUG_STREAM diff --git a/sources/shiboken6/ApiExtractor/abstractmetalang.h b/sources/shiboken6/ApiExtractor/abstractmetalang.h index d5071a28..3dc87669 100644 --- a/sources/shiboken6/ApiExtractor/abstractmetalang.h +++ b/sources/shiboken6/ApiExtractor/abstractmetalang.h @@ -66,12 +66,13 @@ public: ~AbstractMetaClass(); const AbstractMetaFunctionCList &functions() const; + const AbstractMetaFunctionCList &userAddedPythonOverrides() const; void setFunctions(const AbstractMetaFunctionCList &functions); static void addFunction(const AbstractMetaClassPtr &klass, const AbstractMetaFunctionCPtr &function); bool hasFunction(const QString &str) const; - AbstractMetaFunctionCPtr findFunction(QStringView functionName) const; - AbstractMetaFunctionCList findFunctions(QStringView functionName) const; + AbstractMetaFunctionCPtr findFunction(QAnyStringView functionName) const; + AbstractMetaFunctionCList findFunctions(QAnyStringView functionName) const; AbstractMetaFunctionCPtr findOperatorBool() const; // Find a Qt-style isNull() method suitable for nb_bool AbstractMetaFunctionCPtr findQtIsNullMethod() const; @@ -315,9 +316,9 @@ public: bool avoidProtectedHack); static AbstractMetaClassPtr findClass(const AbstractMetaClassList &classes, - QStringView name); + QAnyStringView name); static AbstractMetaClassCPtr findClass(const AbstractMetaClassCList &classes, - QStringView name); + QAnyStringView name); static AbstractMetaClassPtr findClass(const AbstractMetaClassList &classes, const TypeEntryCPtr &typeEntry); static AbstractMetaClassCPtr findClass(const AbstractMetaClassCList &classes, @@ -344,16 +345,14 @@ private: void format(QDebug &d) const; void formatMembers(QDebug &d) const; friend QDebug operator<<(QDebug d, const AbstractMetaClassCPtr &ac); + friend QDebug operator<<(QDebug d, const AbstractMetaClassPtr &ac); + friend QDebug operator<<(QDebug d, const AbstractMetaClass *ac); + friend void formatMetaClass(QDebug &, const AbstractMetaClass *); #endif QScopedPointer d; }; -inline bool AbstractMetaClass::isFinalInTargetLang() const -{ - return attributes().testFlag(FinalInTargetLang); -} - inline bool AbstractMetaClass::isAbstract() const { return attributes().testFlag(Abstract); @@ -371,10 +370,10 @@ void AbstractMetaClass::invisibleNamespaceRecursion(Function f) const } bool inheritsFrom(const AbstractMetaClassCPtr &c, const AbstractMetaClassCPtr &other); -bool inheritsFrom(const AbstractMetaClassCPtr &c, const QString &name); +bool inheritsFrom(const AbstractMetaClassCPtr &c, QAnyStringView name); inline bool isQObject(const AbstractMetaClassCPtr &c) { - return inheritsFrom(c, QStringLiteral("QObject")); + return inheritsFrom(c, "QObject"); } AbstractMetaClassCPtr findBaseClass(const AbstractMetaClassCPtr &c, diff --git a/sources/shiboken6/ApiExtractor/abstractmetalang_enums.h b/sources/shiboken6/ApiExtractor/abstractmetalang_enums.h index 2bdfdb54..9047c6bc 100644 --- a/sources/shiboken6/ApiExtractor/abstractmetalang_enums.h +++ b/sources/shiboken6/ApiExtractor/abstractmetalang_enums.h @@ -11,18 +11,15 @@ enum class FunctionQueryOption { Constructors = 0x0000002, // Constructors except copy/move CopyConstructor = 0x0000004, // Only copy constructors //Destructors = 0x0000002, // Only destructors. Not included in class. - FinalInTargetLangFunctions = 0x0000008, // Only functions that are non-virtual in TargetLang ClassImplements = 0x0000020, // Only functions implemented by the current class StaticFunctions = 0x0000080, // Only static functions Signals = 0x0000100, // Only signals NormalFunctions = 0x0000200, // Only functions that aren't signals Visible = 0x0000400, // Only public and protected functions - WasPublic = 0x0001000, // Only functions that were originally public NonStaticFunctions = 0x0004000, // No static functions Empty = 0x0008000, // Empty overrides of abstract functions Invisible = 0x0010000, // Only private functions VirtualInCppFunctions = 0x0020000, // Only functions that are virtual in C++ - VirtualInTargetLangFunctions = 0x0080000, // Only functions which are virtual in TargetLang NotRemoved = 0x0400000, // Only functions that have not been removed OperatorOverloads = 0x2000000, // Only functions that are operator overloads GenerateExceptionHandling = 0x4000000, diff --git a/sources/shiboken6/ApiExtractor/abstractmetatype.cpp b/sources/shiboken6/ApiExtractor/abstractmetatype.cpp index d9f5bde4..3ec07509 100644 --- a/sources/shiboken6/ApiExtractor/abstractmetatype.cpp +++ b/sources/shiboken6/ApiExtractor/abstractmetatype.cpp @@ -543,6 +543,7 @@ void AbstractMetaType::decideUsagePattern() pattern = ObjectPattern; } setTypeUsagePattern(pattern); + Q_ASSERT(pattern != ContainerPattern || !d->m_instantiations.isEmpty()); } bool AbstractMetaTypeData::hasTemplateChildren() const @@ -778,9 +779,9 @@ bool AbstractMetaTypeData::equals(const AbstractMetaTypeData &rhs) const && m_referenceType == rhs.m_referenceType && isEquivalent(rhs); } -bool AbstractMetaType::equals(const AbstractMetaType &rhs) const +bool comparesEqual(const AbstractMetaType &lhs, const AbstractMetaType &rhs) noexcept { - return d->equals(*rhs.d); + return lhs.d->equals(*rhs.d); } bool AbstractMetaType::isEquivalent(const AbstractMetaType &rhs) const @@ -948,14 +949,18 @@ using AbstractMetaTypeCache = QHash; Q_GLOBAL_STATIC(AbstractMetaTypeCache, metaTypeFromStringCache) std::optional -AbstractMetaType::fromString(QString typeSignature, QString *errorMessage) +AbstractMetaType::fromString(const QString &typeSignatureIn, QString *errorMessage) { - typeSignature = typeSignature.trimmed(); + auto &cache = *metaTypeFromStringCache(); + auto it = cache.find(typeSignatureIn); + if (it != cache.end()) + return it.value(); + + QString typeSignature = typeSignatureIn.trimmed(); if (typeSignature.startsWith(u"::")) typeSignature.remove(0, 2); - auto &cache = *metaTypeFromStringCache(); - auto it = cache.find(typeSignature); + it = cache.find(typeSignature); if (it == cache.end()) { auto metaType = AbstractMetaBuilder::translateType(typeSignature, nullptr, {}, errorMessage); @@ -964,6 +969,8 @@ AbstractMetaType::fromString(QString typeSignature, QString *errorMessage) errorMessage->prepend(msgCannotBuildMetaType(typeSignature)); return {}; } + if (typeSignature != typeSignatureIn) + cache.insert(typeSignatureIn, metaType.value()); it = cache.insert(typeSignature, metaType.value()); } return it.value(); diff --git a/sources/shiboken6/ApiExtractor/abstractmetatype.h b/sources/shiboken6/ApiExtractor/abstractmetatype.h index dd3d96bf..8a1ecdf2 100644 --- a/sources/shiboken6/ApiExtractor/abstractmetatype.h +++ b/sources/shiboken6/ApiExtractor/abstractmetatype.h @@ -9,6 +9,7 @@ #include "parser/codemodel_enums.h" #include "typedatabase_typedefs.h" +#include #include #include #include @@ -171,7 +172,6 @@ public: bool hasTemplateChildren() const; - bool equals(const AbstractMetaType &rhs) const; /// Is equivalent from the POV of argument passing (differ by const ref) bool isEquivalent(const AbstractMetaType &rhs) const; @@ -188,7 +188,7 @@ public: /// \param typeSignature The string describing the type to be built. /// \return A new AbstractMetaType object or nullopt in case of failure. static std::optional - fromString(QString typeSignature, QString *errorMessage = nullptr); + fromString(const QString &typeSignatureIn, QString *errorMessage = nullptr); /// Creates an AbstractMetaType object from a TypeEntry. static AbstractMetaType fromTypeEntry(const TypeEntryCPtr &typeEntry); /// Creates an AbstractMetaType object from an AbstractMetaClass. @@ -256,6 +256,9 @@ public: private: friend size_t qHash(const AbstractMetaType &t, size_t seed = 0) noexcept { return qHash(t.typeEntry().get(), seed); } + friend bool comparesEqual(const AbstractMetaType &lhs, + const AbstractMetaType &rhs) noexcept; + Q_DECLARE_EQUALITY_COMPARABLE(AbstractMetaType) friend class AbstractMetaTypeData; QSharedDataPointer d; @@ -265,11 +268,6 @@ private: QString formatPythonSignature() const; }; -inline bool operator==(const AbstractMetaType &t1, const AbstractMetaType &t2) -{ return t1.equals(t2); } -inline bool operator!=(const AbstractMetaType &t1, const AbstractMetaType &t2) -{ return !t1.equals(t2); } - #ifndef QT_NO_DEBUG_STREAM QDebug operator<<(QDebug d, const AbstractMetaType &at); QDebug operator<<(QDebug d, const AbstractMetaType *at); diff --git a/sources/shiboken6/ApiExtractor/addedfunction.cpp b/sources/shiboken6/ApiExtractor/addedfunction.cpp index efb5fb99..9d95b734 100644 --- a/sources/shiboken6/ApiExtractor/addedfunction.cpp +++ b/sources/shiboken6/ApiExtractor/addedfunction.cpp @@ -9,18 +9,13 @@ using namespace Qt::StringLiterals; -static inline QString callOperator() { return QStringLiteral("operator()"); } +constexpr auto callOperator = "operator()"_L1; // Helpers to split a parameter list of , // (@ denoting names), like // "void foo(QList &@list@ = QList{1,2}, int @b@=5, ...)" namespace AddedFunctionParser { -bool Argument::equals(const Argument &rhs) const -{ - return type == rhs.type && name == rhs.name && defaultValue == rhs.defaultValue; -} - QDebug operator<<(QDebug d, const Argument &a) { QDebugStateSaver saver(d); @@ -143,8 +138,8 @@ AddedFunction::AddedFunctionPtr QStringView signature = QStringView{signatureIn}.trimmed(); // Skip past "operator()(...)" - const auto parenSearchStartPos = signature.startsWith(callOperator()) - ? callOperator().size() : 0; + const auto parenSearchStartPos = signature.startsWith(callOperator) + ? callOperator.size() : 0; const auto openParenPos = signature.indexOf(u'(', parenSearchStartPos); if (openParenPos < 0) { return AddedFunctionPtr(new AddedFunction(signature.toString(), diff --git a/sources/shiboken6/ApiExtractor/addedfunction.h b/sources/shiboken6/ApiExtractor/addedfunction.h index 8a0475f3..b8d189b7 100644 --- a/sources/shiboken6/ApiExtractor/addedfunction.h +++ b/sources/shiboken6/ApiExtractor/addedfunction.h @@ -79,6 +79,9 @@ struct AddedFunction bool isDeclaration() const { return m_isDeclaration; } // void setDeclaration(bool value) { m_isDeclaration = value; } + bool isPythonOverride() const { return m_isPythonOverride; } + void setPythonOverride(bool o) { m_isPythonOverride = o; } + const FunctionModificationList &modifications() const { return m_modifications; } FunctionModificationList &modifications() { return m_modifications; } @@ -86,17 +89,22 @@ struct AddedFunction DocModificationList &docModifications() { return m_docModifications; } void addDocModification(const DocModification &m) { m_docModifications.append(m); } + QString targetLangPackage() const { return m_targetLangPackage; } + void setTargetLangPackage(const QString &p) { m_targetLangPackage = p; } + private: QString m_name; QList m_arguments; TypeInfo m_returnType; FunctionModificationList m_modifications; DocModificationList m_docModifications; + QString m_targetLangPackage; Access m_access = Public; bool m_isConst = false; bool m_isClassMethod = false; bool m_isStatic = false; bool m_isDeclaration = false; + bool m_isPythonOverride = false; }; QDebug operator<<(QDebug d, const AddedFunction::Argument &a); diff --git a/sources/shiboken6/ApiExtractor/addedfunction_p.h b/sources/shiboken6/ApiExtractor/addedfunction_p.h index 9915871a..40b69a5d 100644 --- a/sources/shiboken6/ApiExtractor/addedfunction_p.h +++ b/sources/shiboken6/ApiExtractor/addedfunction_p.h @@ -4,6 +4,7 @@ #ifndef ADDEDFUNCTION_P_H #define ADDEDFUNCTION_P_H +#include #include #include #include @@ -19,18 +20,20 @@ namespace AddedFunctionParser { struct Argument { - bool equals(const Argument &rhs) const; - QString type; QString name; QString defaultValue; + + friend bool comparesEqual(const Argument &lhs, const Argument &rhs) noexcept + { + return lhs.type == rhs.type && lhs.name == rhs.name + && lhs.defaultValue == rhs.defaultValue; + } + Q_DECLARE_EQUALITY_COMPARABLE(Argument) }; using Arguments = QList; -inline bool operator==(const Argument &a1, const Argument &a2) { return a1.equals(a2); } -inline bool operator!=(const Argument &a1, const Argument &a2) { return !a1.equals(a2); } - #ifndef QT_NO_DEBUG_STREAM QDebug operator<<(QDebug d, const Argument &a); #endif diff --git a/sources/shiboken6/ApiExtractor/anystringview_helpers.cpp b/sources/shiboken6/ApiExtractor/anystringview_helpers.cpp new file mode 100644 index 00000000..35d2d535 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/anystringview_helpers.cpp @@ -0,0 +1,56 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "anystringview_helpers.h" + +#include // Must go before QAnyStringView for operator<<(QTextStream,QASV)! +#include +#include +#include + +#include + +QTextStream &operator<<(QTextStream &str, QAnyStringView asv) +{ + asv.visit([&str](auto s) { str << s; }); + return str; +} + +static bool asv_containsImpl(QLatin1StringView v, char c) +{ + return v.contains(uint16_t(c)); +} + +static bool asv_containsImpl(QUtf8StringView v, char c) +{ + return std::strchr(v.data(), c) != nullptr; +} + +static bool asv_containsImpl(QStringView v, char c) +{ + return v.contains(uint16_t(c)); +} + +bool asv_contains(QAnyStringView asv, char needle) +{ + return asv.visit([needle](auto s) { return asv_containsImpl(s, needle); }); +} + +static bool asv_containsImpl(QLatin1StringView v, const char *c) +{ + return v.contains(QLatin1StringView(c)); +} +static bool asv_containsImpl(QUtf8StringView v, const char *c) +{ + return std::strstr(v.data(), c) != nullptr; +} + +static bool asv_containsImpl(QStringView v, const char *c) +{ + return v.contains(QLatin1StringView(c)); +} + +bool asv_contains(QAnyStringView asv, const char *needle) +{ + return asv.visit([needle](auto s) { return asv_containsImpl(s, needle); }); +} diff --git a/sources/shiboken6/ApiExtractor/anystringview_helpers.h b/sources/shiboken6/ApiExtractor/anystringview_helpers.h new file mode 100644 index 00000000..e1e6ab7f --- /dev/null +++ b/sources/shiboken6/ApiExtractor/anystringview_helpers.h @@ -0,0 +1,18 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ANYSTRINGVIEW_STREAM_H +#define ANYSTRINGVIEW_STREAM_H + +#include + +QT_FORWARD_DECLARE_CLASS(QAnyStringView) +QT_FORWARD_DECLARE_CLASS(QTextStream) +QT_FORWARD_DECLARE_CLASS(QDebug) + +QTextStream &operator<<(QTextStream &str, QAnyStringView asv); + +bool asv_contains(QAnyStringView asv, char needle); +bool asv_contains(QAnyStringView asv, const char *needle); + +#endif // ANYSTRINGVIEW_STREAM_H diff --git a/sources/shiboken6/ApiExtractor/apiextractor.cpp b/sources/shiboken6/ApiExtractor/apiextractor.cpp index cf1b59b8..a4efaf2f 100644 --- a/sources/shiboken6/ApiExtractor/apiextractor.cpp +++ b/sources/shiboken6/ApiExtractor/apiextractor.cpp @@ -340,8 +340,7 @@ bool ApiExtractorPrivate::runHelper(ApiExtractorFlags flags) } const QString pattern = QDir::tempPath() + u'/' - + m_cppFileNames.constFirst().baseName() - + QStringLiteral("_XXXXXX.hpp"); + + m_cppFileNames.constFirst().baseName() + "_XXXXXX.hpp"_L1; QTemporaryFile ppFile(pattern); bool autoRemove = !qEnvironmentVariableIsSet("KEEP_TEMP_FILES"); // make sure that a tempfile can be written @@ -422,6 +421,7 @@ std::optional ApiExtractor::run(ApiExtractorFlags flags) result.m_globalEnums = d->m_builder->globalEnums(); result.m_enums = d->m_builder->typeEntryToEnumsHash(); result.m_flags = flags; + result.m_typedefTargetToName = d->m_builder->typedefTargetToName(); qSwap(result.m_instantiatedContainers, collectContext.instantiatedContainers); qSwap(result.m_instantiatedSmartPointers, collectContext.instantiatedSmartPointers); return result; @@ -456,11 +456,10 @@ AbstractMetaFunctionPtr AbstractMetaClassPtr ApiExtractor::inheritTemplateClass(const ComplexTypeEntryPtr &te, const AbstractMetaClassCPtr &templateClass, - const AbstractMetaTypeList &templateTypes, - InheritTemplateFlags flags) + const AbstractMetaTypeList &templateTypes) { return AbstractMetaBuilder::inheritTemplateClass(te, templateClass, - templateTypes, flags); + templateTypes); } QString ApiExtractorPrivate::getSimplifiedContainerTypeName(const AbstractMetaType &type) @@ -534,12 +533,12 @@ ApiExtractorPrivate::addInstantiatedContainersAndSmartPointers(InstantiationColl return; } if (type.hasTemplateChildren()) { - QString piece = isContainer ? QStringLiteral("container") : QStringLiteral("smart pointer"); + const auto piece = isContainer ? "container"_L1 : "smart pointer"_L1; QString warning = QString::fromLatin1("Skipping instantiation of %1 '%2' because it has template" " arguments.").arg(piece, type.originalTypeDescription()); if (!contextName.isEmpty()) - warning.append(QStringLiteral(" Calling context: ") + contextName); + warning.append(" Calling context: "_L1 + contextName); qCWarning(lcShiboken).noquote().nospace() << warning; return; @@ -606,7 +605,6 @@ void ApiExtractorPrivate::addInstantiatedSmartPointer(InstantiationCollectContex const auto ste = std::static_pointer_cast(smp.smartPointer->typeEntry()); QString name = ste->getTargetName(smp.type); auto parentTypeEntry = ste->parent(); - InheritTemplateFlags flags; auto colonPos = name.lastIndexOf(u"::"); const bool withinNameSpace = colonPos != -1; @@ -617,19 +615,18 @@ void ApiExtractorPrivate::addInstantiatedSmartPointer(InstantiationCollectContex if (nameSpaces.isEmpty()) throw Exception(msgNamespaceNotFound(name)); parentTypeEntry = nameSpaces.constFirst(); - } else { - flags.setFlag(InheritTemplateFlag::SetEnclosingClass); } TypedefEntryPtr typedefEntry(new TypedefEntry(name, ste->name(), ste->version(), parentTypeEntry)); typedefEntry->setTargetLangPackage(ste->targetLangPackage()); auto instantiationEntry = TypeDatabase::initializeTypeDefEntry(typedefEntry, ste); + instantiationEntry->setParent(parentTypeEntry); smp.specialized = ApiExtractor::inheritTemplateClass(instantiationEntry, smp.smartPointer, - {instantiatedType}, flags); + {instantiatedType}); Q_ASSERT(smp.specialized); - if (withinNameSpace) { // move class to desired namespace + if (parentTypeEntry->type() != TypeEntry::TypeSystemType) { // move class to desired namespace const auto enclClass = AbstractMetaClass::findClass(m_builder->classes(), parentTypeEntry); Q_ASSERT(enclClass); auto specialized = std::const_pointer_cast(smp.specialized); @@ -669,6 +666,8 @@ ApiExtractorPrivate::collectInstantiatedContainersAndSmartPointers(Instantiation return; for (const auto &func : metaClass->functions()) collectInstantiatedContainersAndSmartPointers(context, func); + for (const auto &func : metaClass->userAddedPythonOverrides()) + collectInstantiatedContainersAndSmartPointers(context, func); for (const AbstractMetaField &field : metaClass->fields()) addInstantiatedContainersAndSmartPointers(context, field.type(), field.name()); diff --git a/sources/shiboken6/ApiExtractor/apiextractor.h b/sources/shiboken6/ApiExtractor/apiextractor.h index feae9454..7bff5c25 100644 --- a/sources/shiboken6/ApiExtractor/apiextractor.h +++ b/sources/shiboken6/ApiExtractor/apiextractor.h @@ -73,8 +73,7 @@ public: static AbstractMetaClassPtr inheritTemplateClass(const ComplexTypeEntryPtr &te, const AbstractMetaClassCPtr &templateClass, - const AbstractMetaTypeList &templateTypes, - InheritTemplateFlags flags = {}); + const AbstractMetaTypeList &templateTypes); private: ApiExtractorPrivate *d; diff --git a/sources/shiboken6/ApiExtractor/apiextractorflags.h b/sources/shiboken6/ApiExtractor/apiextractorflags.h index 4fe6ecc1..6f69b8b7 100644 --- a/sources/shiboken6/ApiExtractor/apiextractorflags.h +++ b/sources/shiboken6/ApiExtractor/apiextractorflags.h @@ -15,12 +15,4 @@ enum class ApiExtractorFlag Q_DECLARE_FLAGS(ApiExtractorFlags, ApiExtractorFlag) Q_DECLARE_OPERATORS_FOR_FLAGS(ApiExtractorFlags) -enum class InheritTemplateFlag -{ - SetEnclosingClass = 0x1 -}; - -Q_DECLARE_FLAGS(InheritTemplateFlags, InheritTemplateFlag) -Q_DECLARE_OPERATORS_FOR_FLAGS(InheritTemplateFlags) - #endif // APIEXTRACTORFLAGS_H diff --git a/sources/shiboken6/ApiExtractor/apiextractorresult.cpp b/sources/shiboken6/ApiExtractor/apiextractorresult.cpp index eb9ac2d4..5a433bbe 100644 --- a/sources/shiboken6/ApiExtractor/apiextractorresult.cpp +++ b/sources/shiboken6/ApiExtractor/apiextractorresult.cpp @@ -7,6 +7,7 @@ #include "enumtypeentry.h" #include "flagstypeentry.h" +#include "smartpointertypeentry.h" ApiExtractorResult::ApiExtractorResult() = default; @@ -50,6 +51,23 @@ const InstantiatedSmartPointers &ApiExtractorResult::instantiatedSmartPointers() return m_instantiatedSmartPointers; } +std::optional + ApiExtractorResult::findSmartPointerInstantiation(const SmartPointerTypeEntryCPtr &pointer, + const TypeEntryCPtr &pointee) const +{ + for (const auto &smp : m_instantiatedSmartPointers) { + const auto &i = smp.type; + if (i.typeEntry() == pointer && i.instantiations().at(0).typeEntry() == pointee) + return smp; + } + return std::nullopt; +} + +const QMultiHash &ApiExtractorResult::typedefTargetToName() const +{ + return m_typedefTargetToName; +} + ApiExtractorFlags ApiExtractorResult::flags() const { return m_flags; diff --git a/sources/shiboken6/ApiExtractor/apiextractorresult.h b/sources/shiboken6/ApiExtractor/apiextractorresult.h index c1bb1a0a..b2aae88e 100644 --- a/sources/shiboken6/ApiExtractor/apiextractorresult.h +++ b/sources/shiboken6/ApiExtractor/apiextractorresult.h @@ -10,6 +10,7 @@ #include "typesystem_typedefs.h" #include +#include #include @@ -42,6 +43,11 @@ public: const AbstractMetaTypeList &instantiatedContainers() const; const InstantiatedSmartPointers &instantiatedSmartPointers() const; + std::optional + findSmartPointerInstantiation(const SmartPointerTypeEntryCPtr &pointer, + const TypeEntryCPtr &pointee) const; + + const QMultiHash &typedefTargetToName() const; // Query functions for the generators std::optional @@ -66,6 +72,7 @@ private: AbstractMetaTypeList m_instantiatedContainers; InstantiatedSmartPointers m_instantiatedSmartPointers; QHash m_enums; + QMultiHash m_typedefTargetToName; ApiExtractorFlags m_flags; friend class ApiExtractor; diff --git a/sources/shiboken6/ApiExtractor/clangparser/clangbuilder.cpp b/sources/shiboken6/ApiExtractor/clangparser/clangbuilder.cpp index 57604cea..31e7efb0 100644 --- a/sources/shiboken6/ApiExtractor/clangparser/clangbuilder.cpp +++ b/sources/shiboken6/ApiExtractor/clangparser/clangbuilder.cpp @@ -26,8 +26,6 @@ using namespace Qt::StringLiterals; namespace clang { -static inline QString templateBrackets() { return QStringLiteral("<>"); } - static inline bool isClassCursor(const CXCursor &c) { return c.kind == CXCursor_ClassDecl || c.kind == CXCursor_StructDecl @@ -274,7 +272,7 @@ static QString msgCannotDetermineException(const std::string_view &snippetV) const qsizetype length = qsizetype(truncate ? newLine : snippetV.size()); QString snippet = QString::fromUtf8(snippetV.data(), length); if (truncate) - snippet += QStringLiteral("..."); + snippet += "..."_L1; return u"Cannot determine exception specification: \""_s + snippet + u'"'; } @@ -348,7 +346,7 @@ FunctionModelItem BuilderPrivate::createFunction(const CXCursor &cursor, case CXAvailability_Available: break; case CXAvailability_Deprecated: - result->setDeprecated(true); + result->setAttribute(FunctionAttribute::Deprecated); break; case CXAvailability_NotAvailable: // "Foo(const Foo&) = delete;" result->setDeleted(true); @@ -391,9 +389,9 @@ FunctionModelItem BuilderPrivate::createMemberFunction(const CXCursor &cursor, auto result = createFunction(cursor, functionType, isTemplateCode); result->setAccessPolicy(accessPolicy(clang_getCXXAccessSpecifier(cursor))); result->setConstant(clang_CXXMethod_isConst(cursor) != 0); - result->setStatic(clang_CXXMethod_isStatic(cursor) != 0); - result->setVirtual(clang_CXXMethod_isVirtual(cursor) != 0); - result->setAbstract(clang_CXXMethod_isPureVirtual(cursor) != 0); + result->setAttribute(FunctionAttribute::Static, clang_CXXMethod_isStatic(cursor) != 0); + result->setAttribute(FunctionAttribute::Virtual, clang_CXXMethod_isVirtual(cursor) != 0); + result->setAttribute(FunctionAttribute::Abstract, clang_CXXMethod_isPureVirtual(cursor) != 0); return result; } @@ -409,7 +407,8 @@ void BuilderPrivate::qualifyConstructor(const CXCursor &cursor) && m_currentFunction->arguments().size() == 1 && clang_CXXConstructor_isCopyConstructor(cursor) == 0 && clang_CXXConstructor_isMoveConstructor(cursor) == 0) { - m_currentFunction->setExplicit(clang_CXXConstructor_isConvertingConstructor(cursor) == 0); + m_currentFunction->setAttribute(FunctionAttribute::Explicit, + clang_CXXConstructor_isConvertingConstructor(cursor) == 0); } } @@ -433,6 +432,7 @@ void BuilderPrivate::addField(const CXCursor &cursor) field->setScope(m_scope); field->setType(createTypeInfo(cursor)); field->setMutable(clang_CXXField_isMutable(cursor) != 0); + setFileName(cursor, field.get()); m_currentField = field; m_scopeStack.back()->addVariable(field); } @@ -969,15 +969,15 @@ BaseVisitor::StartTokenResult Builder::startToken(const CXCursor &cursor) || !d->addClass(cursor, CodeModel::Class)) { return Skip; } - d->m_currentClass->setName(d->m_currentClass->name() + templateBrackets()); - d->m_scope.back() += templateBrackets(); + d->m_currentClass->setName(d->m_currentClass->name() + "<>"_L1); + d->m_scope.back() += "<>"_L1; break; case CXCursor_EnumDecl: { QString name = enumType(cursor); EnumKind kind = CEnum; if (name.isEmpty()) { kind = AnonymousEnum; - name = QStringLiteral("enum_") + QString::number(++d->m_anonymousEnumCount); + name = "enum_"_L1 + QString::number(++d->m_anonymousEnumCount); #if !CLANG_NO_ENUMDECL_ISSCOPED } else if (clang_EnumDecl_isScoped(cursor) != 0) { #else @@ -1059,6 +1059,7 @@ BaseVisitor::StartTokenResult Builder::startToken(const CXCursor &cursor) } } d->m_currentFunction = d->createFunction(cursor, CodeModel::Normal, true); + d->setFileName(cursor, d->m_currentFunction.get()); d->m_scopeStack.back()->addFunction(d->m_currentFunction); break; case CXCursor_FunctionDecl: @@ -1134,8 +1135,8 @@ BaseVisitor::StartTokenResult Builder::startToken(const CXCursor &cursor) const QString &tplParmName = tItem->name(); if (Q_UNLIKELY(!insertTemplateParameterIntoClassName(tplParmName, d->m_currentClass) || !insertTemplateParameterIntoClassName(tplParmName, &d->m_scope.back()))) { - const QString message = QStringLiteral("Error inserting template parameter \"") + tplParmName - + QStringLiteral("\" into ") + d->m_currentClass->name(); + const QString message = "Error inserting template parameter \""_L1 + tplParmName + + "\" into "_L1 + d->m_currentClass->name(); const Diagnostic d(message, cursor, CXDiagnostic_Error); qWarning() << d; appendDiagnostic(d); @@ -1182,13 +1183,13 @@ BaseVisitor::StartTokenResult Builder::startToken(const CXCursor &cursor) break; case CXCursor_CXXFinalAttr: if (d->m_currentFunction) - d->m_currentFunction->setFinal(true); + d->m_currentFunction->setAttribute(FunctionAttribute::Final); else if (d->m_currentClass) d->m_currentClass->setFinal(true); break; case CXCursor_CXXOverrideAttr: if (d->m_currentFunction) - d->m_currentFunction->setOverride(true); + d->m_currentFunction->setAttribute(FunctionAttribute::Override); break; case CXCursor_StaticAssert: // Check for Q_PROPERTY() (see PySide6/global.h.in for an explanation diff --git a/sources/shiboken6/ApiExtractor/clangparser/clangdebugutils.h b/sources/shiboken6/ApiExtractor/clangparser/clangdebugutils.h index 3a1b91e1..7aac8a57 100644 --- a/sources/shiboken6/ApiExtractor/clangparser/clangdebugutils.h +++ b/sources/shiboken6/ApiExtractor/clangparser/clangdebugutils.h @@ -4,7 +4,7 @@ #ifndef CLANGDEBUGUTILS_H #define CLANGDEBUGUTILS_H -#include +#include #include diff --git a/sources/shiboken6/ApiExtractor/clangparser/clangparser.cpp b/sources/shiboken6/ApiExtractor/clangparser/clangparser.cpp index 9762b352..da693047 100644 --- a/sources/shiboken6/ApiExtractor/clangparser/clangparser.cpp +++ b/sources/shiboken6/ApiExtractor/clangparser/clangparser.cpp @@ -13,6 +13,8 @@ #include #include +using namespace Qt::StringLiterals; + namespace clang { QString SourceFileCache::getFileName(CXFile file) @@ -39,7 +41,7 @@ std::string_view SourceFileCache::getCodeSnippet(const CXCursor &cursor, if (range.first.file != range.second.file) { if (errorMessage) - *errorMessage = QStringLiteral("Range spans several files"); + *errorMessage = "Range spans several files"_L1; return std::string_view(empty, 0); } @@ -48,7 +50,7 @@ std::string_view SourceFileCache::getCodeSnippet(const CXCursor &cursor, const QString fileName = getFileName(range.first.file); if (fileName.isEmpty()) { if (errorMessage) - *errorMessage = QStringLiteral("Range has no file"); + *errorMessage = "Range has no file"_L1; return std::string_view(empty, 0); } QFile file(fileName); diff --git a/sources/shiboken6/ApiExtractor/clangparser/clangutils.cpp b/sources/shiboken6/ApiExtractor/clangparser/clangutils.cpp index 4e1f1ab6..1651e09e 100644 --- a/sources/shiboken6/ApiExtractor/clangparser/clangutils.cpp +++ b/sources/shiboken6/ApiExtractor/clangparser/clangutils.cpp @@ -35,11 +35,6 @@ size_t qHash(const CXType &ct, size_t seed) noexcept namespace clang { -bool SourceLocation::equals(const SourceLocation &rhs) const -{ - return file == rhs.file && offset == rhs.offset; -} - SourceLocation getExpansionLocation(const CXSourceLocation &location) { SourceLocation result; diff --git a/sources/shiboken6/ApiExtractor/clangparser/clangutils.h b/sources/shiboken6/ApiExtractor/clangparser/clangutils.h index eaa04dae..fbbf95f1 100644 --- a/sources/shiboken6/ApiExtractor/clangparser/clangutils.h +++ b/sources/shiboken6/ApiExtractor/clangparser/clangutils.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -49,13 +50,14 @@ struct SourceLocation unsigned line = 0; unsigned column = 0; unsigned offset = 0; -}; - -inline bool operator==(const SourceLocation &l1, const SourceLocation &l2) -{ return l1.equals(l2); } -inline bool operator!=(const SourceLocation &l1, const SourceLocation &l2) -{ return !l1.equals(l2); } + friend constexpr bool comparesEqual(const SourceLocation &lhs, + const SourceLocation &rhs) noexcept + { + return lhs.file == rhs.file && lhs.offset == rhs.offset; + } + Q_DECLARE_EQUALITY_COMPARABLE_LITERAL_TYPE(SourceLocation) +}; SourceLocation getExpansionLocation(const CXSourceLocation &location); diff --git a/sources/shiboken6/ApiExtractor/clangparser/compilersupport.cpp b/sources/shiboken6/ApiExtractor/clangparser/compilersupport.cpp index d6137391..f4d04eac 100644 --- a/sources/shiboken6/ApiExtractor/clangparser/compilersupport.cpp +++ b/sources/shiboken6/ApiExtractor/clangparser/compilersupport.cpp @@ -94,13 +94,19 @@ bool setPlatform(const QString &name) return result; } +// 3/2024: Use a recent MSVC2022 for libclang 18.X +static QByteArray msvcCompatVersion() +{ + return libClangVersion() >= QVersionNumber(0, 64) ? "19.39"_ba : "19.26"_ba; +} + static bool runProcess(const QString &program, const QStringList &arguments, QByteArray *stdOutIn = nullptr, QByteArray *stdErrIn = nullptr) { QProcess process; process.start(program, arguments, QProcess::ReadWrite); if (!process.waitForStarted()) { - qWarning().noquote().nospace() << "Unable to start " + qCWarning(lcShiboken).noquote().nospace() << "Unable to start " << process.program() << ": " << process.errorString(); return false; } @@ -113,18 +119,18 @@ static bool runProcess(const QString &program, const QStringList &arguments, *stdOutIn = process.readAllStandardOutput(); if (!finished) { - qWarning().noquote().nospace() << process.program() << " timed out: " << stdErr; + qCWarning(lcShiboken).noquote().nospace() << process.program() << " timed out: " << stdErr; process.kill(); return false; } if (process.exitStatus() != QProcess::NormalExit) { - qWarning().noquote().nospace() << process.program() << " crashed: " << stdErr; + qCWarning(lcShiboken).noquote().nospace() << process.program() << " crashed: " << stdErr; return false; } if (process.exitCode() != 0) { - qWarning().noquote().nospace() << process.program() << " exited " + qCWarning(lcShiboken).noquote().nospace() << process.program() << " exited " << process.exitCode() << ": " << stdErr; return false; } @@ -257,8 +263,8 @@ static QString queryLlvmConfigDir(const QString &arg) return {}; const QString path = QFile::decodeName(stdOut.trimmed()); if (!QFileInfo::exists(path)) { - qWarning(R"(%s: "%s" as returned by llvm-config "%s" does not exist.)", - __FUNCTION__, qPrintable(QDir::toNativeSeparators(path)), qPrintable(arg)); + qCWarning(lcShiboken, R"(%s: "%s" as returned by llvm-config "%s" does not exist.)", + __FUNCTION__, qPrintable(QDir::toNativeSeparators(path)), qPrintable(arg)); return {}; } return path; @@ -271,7 +277,8 @@ static QString findClangLibDir() const QString path = QFile::decodeName(qgetenv(envVar)) + u"/lib"_s; if (QFileInfo::exists(path)) return path; - qWarning("%s: %s as pointed to by %s does not exist.", __FUNCTION__, qPrintable(path), envVar); + qCWarning(lcShiboken, "%s: %s as pointed to by %s does not exist.", + __FUNCTION__, qPrintable(path), envVar); } } return queryLlvmConfigDir(u"--libdir"_s); @@ -283,13 +290,23 @@ static QString findClangBuiltInIncludesDir() const QString clangPathLibDir = findClangLibDir(); if (!clangPathLibDir.isEmpty()) { QString candidate; + QString clangDirName = clangPathLibDir + u"/clang"_s; + // PYSIDE-2769: llvm-config --libdir may report /usr/lib64 on manylinux_2_28_x86_64 + // whereas the includes are under /usr/lib/clang/../include. + if (!QFileInfo::exists(clangDirName) && clangPathLibDir.endsWith("64"_L1)) { + const QString fallback = clangPathLibDir.sliced(0, clangPathLibDir.size() - 2); + clangDirName = fallback + u"/clang"_s; + qCWarning(lcShiboken, "%s: Falling back from %s to %s.", + __FUNCTION__, qPrintable(clangPathLibDir), qPrintable(fallback)); + } + QVersionNumber lastVersionNumber(1, 0, 0); - const QString clangDirName = clangPathLibDir + u"/clang"_s; QDir clangDir(clangDirName); const QFileInfoList versionDirs = clangDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); if (versionDirs.isEmpty()) - qWarning("%s: No subdirectories found in %s.", __FUNCTION__, qPrintable(clangDirName)); + qCWarning(lcShiboken, "%s: No subdirectories found in %s.", + __FUNCTION__, qPrintable(clangDirName)); for (const QFileInfo &fi : versionDirs) { const QString fileName = fi.fileName(); if (fileName.at(0).isDigit()) { @@ -301,22 +318,44 @@ static QString findClangBuiltInIncludesDir() } } if (!candidate.isEmpty()) - return candidate + QStringLiteral("/include"); + return candidate + "/include"_L1; } return queryLlvmConfigDir(u"--includedir"_s); } +QString compilerFromCMake() +{ +#ifdef CMAKE_CXX_COMPILER + return QString::fromLocal8Bit(CMAKE_CXX_COMPILER); +#else + return {}; +#endif +} + +// Return a compiler suitable for determining the internal include paths static QString compilerFromCMake(const QString &defaultCompiler) { if (!compilerPath().isEmpty()) return compilerPath(); -// Added !defined(Q_OS_DARWIN) due to PYSIDE-1032 - QString result = defaultCompiler; -#ifdef CMAKE_CXX_COMPILER - if (platform() != Platform::macOS) - result = QString::fromLocal8Bit(CMAKE_CXX_COMPILER); -#endif - return result; + // Exclude macOS since cmakeCompiler returns the full path instead of the + // /usr/bin/clang shim, which results in the default SDK sysroot path + // missing (PYSIDE-1032) + if (platform() == Platform::macOS) + return defaultCompiler; + QString cmakeCompiler = compilerFromCMake(); + if (cmakeCompiler.isEmpty()) + return defaultCompiler; + QFileInfo fi(cmakeCompiler); + // Should be absolute by default, but a user may specify -DCMAKE_CXX_COMPILER=cl.exe + if (fi.isRelative()) + return cmakeCompiler; + if (fi.exists()) + return fi.absoluteFilePath(); + // The compiler may not exist in case something like icecream or + // a non-standard-path was used on the build machine. Check + // the executable. + cmakeCompiler = QStandardPaths::findExecutable(fi.fileName()); + return cmakeCompiler.isEmpty() ? defaultCompiler : cmakeCompiler; } static void appendClangBuiltinIncludes(HeaderPaths *p) @@ -343,9 +382,10 @@ QByteArrayList emulatedCompilerOptions() HeaderPaths headerPaths; switch (compiler()) { case Compiler::Msvc: - result.append(QByteArrayLiteral("-fms-compatibility-version=19.26.28806")); + result.append("-fms-compatibility-version="_ba + msvcCompatVersion()); result.append(QByteArrayLiteral("-fdelayed-template-parsing")); result.append(QByteArrayLiteral("-Wno-microsoft-enum-value")); + result.append("/Zc:__cplusplus"_ba); // Fix yvals_core.h: STL1000: Unexpected compiler version, expected Clang 7 or newer (MSVC2017 update) result.append(QByteArrayLiteral("-D_ALLOW_COMPILER_AND_STL_VERSION_MISMATCH")); if (needsClangBuiltinIncludes()) diff --git a/sources/shiboken6/ApiExtractor/clangparser/compilersupport.h b/sources/shiboken6/ApiExtractor/clangparser/compilersupport.h index 18e87c49..462e8f20 100644 --- a/sources/shiboken6/ApiExtractor/clangparser/compilersupport.h +++ b/sources/shiboken6/ApiExtractor/clangparser/compilersupport.h @@ -44,6 +44,8 @@ QByteArrayList detectVulkan(); Compiler compiler(); bool setCompiler(const QString &name); +QString compilerFromCMake(); + const QString &compilerPath(); void setCompilerPath(const QString &name); diff --git a/sources/shiboken6/ApiExtractor/classdocumentation.cpp b/sources/shiboken6/ApiExtractor/classdocumentation.cpp index 2008d83d..637e4a42 100644 --- a/sources/shiboken6/ApiExtractor/classdocumentation.cpp +++ b/sources/shiboken6/ApiExtractor/classdocumentation.cpp @@ -83,44 +83,51 @@ qsizetype ClassDocumentation::indexOfProperty(const QString &name) const return -1; } -enum class WebXmlTag +enum class WebXmlCodeTag { - Class, Description, Enum, Function, Parameter, Property, Typedef, Other + Class, Description, Enum, Function, Header, Parameter, Property, Typedef, Other }; -static WebXmlTag tag(QStringView name) +static WebXmlCodeTag tag(QStringView name) { if (name == u"class" || name == u"namespace") - return WebXmlTag::Class; + return WebXmlCodeTag::Class; if (name == u"enum") - return WebXmlTag::Enum; + return WebXmlCodeTag::Enum; if (name == u"function") - return WebXmlTag::Function; + return WebXmlCodeTag::Function; if (name == u"description") - return WebXmlTag::Description; + return WebXmlCodeTag::Description; + if (name == u"header") + return WebXmlCodeTag::Header; if (name == u"parameter") - return WebXmlTag::Parameter; + return WebXmlCodeTag::Parameter; if (name == u"property") - return WebXmlTag::Property; + return WebXmlCodeTag::Property; if (name == u"typedef") - return WebXmlTag::Typedef; - return WebXmlTag::Other; + return WebXmlCodeTag::Typedef; + return WebXmlCodeTag::Other; } -static void parseWebXmlElement(WebXmlTag tag, const QXmlStreamAttributes &attributes, +static void parseWebXmlElement(WebXmlCodeTag tag, const QXmlStreamAttributes &attributes, ClassDocumentation *cd) { switch (tag) { - case WebXmlTag::Class: + case WebXmlCodeTag::Class: cd->name = attributes.value(u"name"_s).toString(); + cd->type = ClassDocumentation::Class; break; - case WebXmlTag::Enum: { + case WebXmlCodeTag::Header: + cd->name = attributes.value(u"name"_s).toString(); + cd->type = ClassDocumentation::Header; + break; + case WebXmlCodeTag::Enum: { EnumDocumentation ed; ed.name = attributes.value(u"name"_s).toString(); cd->enums.append(ed); } break; - case WebXmlTag::Function: { + case WebXmlCodeTag::Function: { FunctionDocumentation fd; fd.name = attributes.value(u"name"_s).toString(); fd.signature = attributes.value(u"signature"_s).toString(); @@ -129,11 +136,11 @@ static void parseWebXmlElement(WebXmlTag tag, const QXmlStreamAttributes &attrib cd->functions.append(fd); } break; - case WebXmlTag::Parameter: + case WebXmlCodeTag::Parameter: Q_ASSERT(!cd->functions.isEmpty()); cd->functions.last().parameters.append(attributes.value(u"type"_s).toString()); break; - case WebXmlTag::Property: { + case WebXmlCodeTag::Property: { PropertyDocumentation pd; pd.name = attributes.value(u"name"_s).toString(); pd.brief = attributes.value(u"brief"_s).toString(); @@ -185,17 +192,17 @@ static QString msgXmlError(const QString &fileName, const QXmlStreamReader &read return result; } -ClassDocumentation parseWebXml(const QString &fileName, QString *errorMessage) +std::optional parseWebXml(const QString &fileName, QString *errorMessage) { ClassDocumentation result; QFile file(fileName); if (!file.open(QIODevice::Text | QIODevice::ReadOnly)) { *errorMessage = msgCannotOpenForReading(file); - return result; + return std::nullopt; } - WebXmlTag lastTag = WebXmlTag::Other; + WebXmlCodeTag lastTag = WebXmlCodeTag::Other; QXmlStreamReader reader(&file); while (!reader.atEnd()) { switch (reader.readNext()) { @@ -203,26 +210,27 @@ ClassDocumentation parseWebXml(const QString &fileName, QString *errorMessage) const auto currentTag = tag(reader.name()); parseWebXmlElement(currentTag, reader.attributes(), &result); switch (currentTag) { // Store relevant tags in lastTag - case WebXmlTag::Class: - case WebXmlTag::Function: - case WebXmlTag::Enum: - case WebXmlTag::Property: - case WebXmlTag::Typedef: + case WebXmlCodeTag::Class: + case WebXmlCodeTag::Function: + case WebXmlCodeTag::Enum: + case WebXmlCodeTag::Header: + case WebXmlCodeTag::Property: + case WebXmlCodeTag::Typedef: lastTag = currentTag; break; - case WebXmlTag::Description: { // Append the description to the element + case WebXmlCodeTag::Description: { // Append the description to the element QString *target = nullptr; switch (lastTag) { - case WebXmlTag::Class: + case WebXmlCodeTag::Class: target = &result.description; break; - case WebXmlTag::Function: + case WebXmlCodeTag::Function: target = &result.functions.last().description; break; - case WebXmlTag::Enum: + case WebXmlCodeTag::Enum: target = &result.enums.last().description; break; - case WebXmlTag::Property: + case WebXmlCodeTag::Property: target = &result.properties.last().description; default: break; @@ -242,7 +250,7 @@ ClassDocumentation parseWebXml(const QString &fileName, QString *errorMessage) if (reader.error() != QXmlStreamReader::NoError) { *errorMessage= msgXmlError(fileName, reader); - return {}; + return std::nullopt; } sortDocumentation(&result); @@ -363,16 +371,11 @@ QDebug operator<<(QDebug debug, const ClassDocumentation &c) QDebugStateSaver saver(debug); debug.noquote(); debug.nospace(); - debug << "Class("; - if (c) { - debug << c.name << ", "; - formatDescription(debug, c.description); - formatList(debug, ", enums", c.enums); - formatList(debug, ", properties", c.properties); - formatList(debug, ", functions", c.functions); - } else { - debug << "invalid"; - } + debug << "Class(" << c.name << ", "; + formatDescription(debug, c.description); + formatList(debug, ", enums", c.enums); + formatList(debug, ", properties", c.properties); + formatList(debug, ", functions", c.functions); debug << ')'; return debug; } diff --git a/sources/shiboken6/ApiExtractor/classdocumentation.h b/sources/shiboken6/ApiExtractor/classdocumentation.h index ef66912f..d4710138 100644 --- a/sources/shiboken6/ApiExtractor/classdocumentation.h +++ b/sources/shiboken6/ApiExtractor/classdocumentation.h @@ -6,6 +6,8 @@ #include +#include + QT_FORWARD_DECLARE_CLASS(QDebug) /// An enumeration in a WebXML/doxygen document @@ -41,9 +43,14 @@ struct FunctionDocumentation : public FunctionDocumentationQuery using FunctionDocumentationList = QList; -/// A class/namespace in a WebXML/doxygen document +/// A WebXML/doxygen document struct ClassDocumentation { + enum Type { + Class, // , class/namespace + Header //
, grouped global functions/enums + }; + qsizetype indexOfEnum(const QString &name) const; FunctionDocumentationList findFunctionCandidates(const QString &name, bool constant) const; @@ -51,18 +58,17 @@ struct ClassDocumentation const FunctionDocumentationQuery &q); qsizetype indexOfProperty(const QString &name) const; + Type type = Type::Class; QString name; QString description; QList enums; QList properties; FunctionDocumentationList functions; - - operator bool() const { return !name.isEmpty(); } }; /// Parse a WebXML class/namespace document -ClassDocumentation parseWebXml(const QString &fileName, QString *errorMessage); +std::optional parseWebXml(const QString &fileName, QString *errorMessage); /// Extract the module description from a WebXML module document QString webXmlModuleDescription(const QString &fileName, QString *errorMessage); diff --git a/sources/shiboken6/ApiExtractor/codesnip.cpp b/sources/shiboken6/ApiExtractor/codesnip.cpp index f79c68ca..e2cd5eb3 100644 --- a/sources/shiboken6/ApiExtractor/codesnip.cpp +++ b/sources/shiboken6/ApiExtractor/codesnip.cpp @@ -63,7 +63,7 @@ void CodeSnipAbstract::purgeEmptyFragments() QRegularExpression CodeSnipAbstract::placeHolderRegex(int index) { - return QRegularExpression(u'%' + QString::number(index) + QStringLiteral("\\b")); + return QRegularExpression(u'%' + QString::number(index) + "\\b"_L1); } void purgeEmptyCodeSnips(QList *list) diff --git a/sources/shiboken6/ApiExtractor/complextypeentry.h b/sources/shiboken6/ApiExtractor/complextypeentry.h index ea38da7f..5b884f2c 100644 --- a/sources/shiboken6/ApiExtractor/complextypeentry.h +++ b/sources/shiboken6/ApiExtractor/complextypeentry.h @@ -42,7 +42,9 @@ public: ForceAbstract = 0x8, // Indicates that the instances are used to create hierarchies // like widgets; parent ownership heuristics are enabled for them. - ParentManagement = 0x10 + ParentManagement = 0x10, + DisableQtMetaObjectFunctions = 0x20, + Typedef = 0x40 // Result of a }; Q_DECLARE_FLAGS(TypeFlags, TypeFlag) diff --git a/sources/shiboken6/ApiExtractor/conditionalstreamreader.cpp b/sources/shiboken6/ApiExtractor/conditionalstreamreader.cpp index c02a9577..b6eda651 100644 --- a/sources/shiboken6/ApiExtractor/conditionalstreamreader.cpp +++ b/sources/shiboken6/ApiExtractor/conditionalstreamreader.cpp @@ -165,15 +165,15 @@ QStringList ConditionalStreamReader::platformConditions() { QStringList result; #if defined (Q_OS_UNIX) - result << QStringLiteral("unix"); + result << "unix"_L1; #endif #if defined (Q_OS_LINUX) - result << QStringLiteral("linux"); + result << "linux"_L1; #elif defined (Q_OS_MACOS) - result << QStringLiteral("darwin"); + result << "darwin"_L1; #elif defined (Q_OS_WINDOWS) - result << QStringLiteral("windows"); + result << "windows"_L1; #endif return result; } diff --git a/sources/shiboken6/ApiExtractor/docparser.cpp b/sources/shiboken6/ApiExtractor/docparser.cpp index dbb09842..468fe109 100644 --- a/sources/shiboken6/ApiExtractor/docparser.cpp +++ b/sources/shiboken6/ApiExtractor/docparser.cpp @@ -31,15 +31,37 @@ using namespace Qt::StringLiterals; -DocParser::DocParser() +static inline bool isXpathDocModification(const DocModification &mod) { -#ifdef HAVE_LIBXSLT - xmlSubstituteEntitiesDefault(1); -#endif + return mod.mode() == TypeSystem::DocModificationXPathReplace; } +static inline bool isNotXpathDocModification(const DocModification &mod) +{ + return mod.mode() != TypeSystem::DocModificationXPathReplace; +} + +static void removeXpathDocModifications(DocModificationList *l) +{ + l->erase(std::remove_if(l->begin(), l->end(), isXpathDocModification), l->end()); +} + +static void removeNonXpathDocModifications(DocModificationList *l) +{ + l->erase(std::remove_if(l->begin(), l->end(), isNotXpathDocModification), l->end()); +} + +DocParser::DocParser() = default; DocParser::~DocParser() = default; +void DocParser::fillGlobalFunctionDocumentation(const AbstractMetaFunctionPtr &) +{ +} + +void DocParser::fillGlobalEnumDocumentation(AbstractMetaEnum &) +{ +} + QString DocParser::getDocumentation(const XQueryPtr &xquery, const QString& query, const DocModificationList& mods) { @@ -86,22 +108,59 @@ bool DocParser::skipForQuery(const AbstractMetaFunctionCPtr &func) usesRValueReference); } -DocModificationList DocParser::getDocModifications(const AbstractMetaClassCPtr &cppClass, - const AbstractMetaFunctionCPtr &func) +DocModificationList DocParser::getDocModifications(const AbstractMetaClassCPtr &cppClass) + +{ + auto result = cppClass->typeEntry()->docModifications(); + removeXpathDocModifications(&result); + return result; +} + +static void filterBySignature(const AbstractMetaFunctionCPtr &func, DocModificationList *l) { - auto te = cppClass->typeEntry(); - if (!func) - return te->docModifications(); + if (!l->isEmpty()) { + const QString minimalSignature = func->minimalSignature(); + const auto filter = [&minimalSignature](const DocModification &mod) { + return mod.signature() != minimalSignature; + }; + l->erase(std::remove_if(l->begin(), l->end(), filter), l->end()); + } +} - if (func->isUserAdded()) - return func->addedFunctionDocModifications(); +DocModificationList DocParser::getDocModifications(const AbstractMetaFunctionCPtr &func, + const AbstractMetaClassCPtr &cppClass) +{ + DocModificationList result; + if (func->isUserAdded()) { + result = func->addedFunctionDocModifications(); + removeXpathDocModifications(&result); + } else if (cppClass != nullptr) { + result = cppClass->typeEntry()->functionDocModifications(); + removeXpathDocModifications(&result); + filterBySignature(func, &result); + } + return result; +} - DocModificationList result = te->functionDocModifications(); - const QString minimalSignature = func->minimalSignature(); - const auto filter = [&minimalSignature](const DocModification &mod) { - return mod.signature() != minimalSignature; - }; - result.erase(std::remove_if(result.begin(), result.end(), filter), result.end()); +DocModificationList DocParser::getXpathDocModifications(const AbstractMetaClassCPtr &cppClass) +{ + auto result = cppClass->typeEntry()->docModifications(); + removeNonXpathDocModifications(&result); + return result; +} + +DocModificationList DocParser::getXpathDocModifications(const AbstractMetaFunctionCPtr &func, + const AbstractMetaClassCPtr &cppClass) +{ + DocModificationList result; + if (func->isUserAdded()) { + result = func->addedFunctionDocModifications(); + removeNonXpathDocModifications(&result); + } else if (cppClass != nullptr) { + result = cppClass->typeEntry()->functionDocModifications(); + removeNonXpathDocModifications(&result); + filterBySignature(func, &result); + } return result; } @@ -131,12 +190,8 @@ AbstractMetaFunctionCList DocParser::documentableFunctions(const AbstractMetaCla return result; } -static inline bool isXpathDocModification(const DocModification &mod) -{ - return mod.mode() == TypeSystem::DocModificationXPathReplace; -} - -QString DocParser::applyDocModifications(const DocModificationList& mods, const QString& xml) +QString DocParser::applyDocModifications(const DocModificationList& xpathMods, + const QString& xml) { const char xslPrefix[] = R"( @@ -150,32 +205,28 @@ R"( )"; - if (mods.isEmpty() || xml.isEmpty() - || !std::any_of(mods.cbegin(), mods.cend(), isXpathDocModification)) { + if (xpathMods.isEmpty() || xml.isEmpty()) return xml; - } QString xsl = QLatin1StringView(xslPrefix); - for (const DocModification &mod : mods) { - if (isXpathDocModification(mod)) { - QString xpath = mod.xpath(); - xpath.replace(u'"', u"""_s); - xsl += u""_s - + mod.code() + u"\n"_s; - } + for (const DocModification &mod : xpathMods) { + Q_ASSERT(isXpathDocModification(mod)); + QString xpath = mod.xpath(); + xpath.replace(u'"', u"""_s); + xsl += ""_L1 + + mod.code() + "\n"_L1; } QString errorMessage; const QString result = xsl_transform(xml, xsl, &errorMessage); if (!errorMessage.isEmpty()) qCWarning(lcShibokenDoc, "%s", - qPrintable(msgXpathDocModificationError(mods, errorMessage))); + qPrintable(msgXpathDocModificationError(xpathMods, errorMessage))); if (result == xml) { const QString message = u"Query did not result in any modifications to \""_s + xml + u'"'; qCWarning(lcShibokenDoc, "%s", - qPrintable(msgXpathDocModificationError(mods, message))); + qPrintable(msgXpathDocModificationError(xpathMods, message))); } return result; } diff --git a/sources/shiboken6/ApiExtractor/docparser.h b/sources/shiboken6/ApiExtractor/docparser.h index b5640d1d..6d458b25 100644 --- a/sources/shiboken6/ApiExtractor/docparser.h +++ b/sources/shiboken6/ApiExtractor/docparser.h @@ -28,6 +28,8 @@ public: DocParser(); virtual ~DocParser(); virtual void fillDocumentation(const AbstractMetaClassPtr &metaClass) = 0; + virtual void fillGlobalFunctionDocumentation(const AbstractMetaFunctionPtr &f); + virtual void fillGlobalEnumDocumentation(AbstractMetaEnum &e); /** * Process and retrieves documentation concerning the entire @@ -94,8 +96,12 @@ public: /// Helper to return the documentation modifications for a class /// or a member function. - static DocModificationList getDocModifications(const AbstractMetaClassCPtr &cppClass, - const AbstractMetaFunctionCPtr &func = {}); + static DocModificationList getDocModifications(const AbstractMetaClassCPtr &cppClass); + static DocModificationList getDocModifications(const AbstractMetaFunctionCPtr &func, + const AbstractMetaClassCPtr &cppClass = {}); + static DocModificationList getXpathDocModifications(const AbstractMetaClassCPtr &cppClass); + static DocModificationList getXpathDocModifications(const AbstractMetaFunctionCPtr &func, + const AbstractMetaClassCPtr &cppClass = {}); static QString enumBaseClass(const AbstractMetaEnum &e); @@ -106,7 +112,7 @@ protected: static AbstractMetaFunctionCList documentableFunctions(const AbstractMetaClassCPtr &metaClass); - static QString applyDocModifications(const DocModificationList &mods, const QString &xml); + static QString applyDocModifications(const DocModificationList &xpathMods, const QString &xml); private: QString m_packageName; diff --git a/sources/shiboken6/ApiExtractor/documentation.cpp b/sources/shiboken6/ApiExtractor/documentation.cpp index 6c4d7166..33cf0e9f 100644 --- a/sources/shiboken6/ApiExtractor/documentation.cpp +++ b/sources/shiboken6/ApiExtractor/documentation.cpp @@ -35,12 +35,6 @@ void Documentation::setFormat(Documentation::Format f) m_format = f; } -bool Documentation::equals(const Documentation &rhs) const -{ - return m_format == rhs.m_format && m_detailed == rhs.m_detailed - && m_brief == rhs.m_brief; -} - void Documentation::setDetailed(const QString &detailed) { m_detailed = detailed.trimmed(); diff --git a/sources/shiboken6/ApiExtractor/documentation.h b/sources/shiboken6/ApiExtractor/documentation.h index a18bb1a1..df9d5d61 100644 --- a/sources/shiboken6/ApiExtractor/documentation.h +++ b/sources/shiboken6/ApiExtractor/documentation.h @@ -5,6 +5,7 @@ #define DOCUMENTATION_H #include +#include QT_FORWARD_DECLARE_CLASS(QDebug) @@ -44,16 +45,19 @@ public: void setBrief(const QString &brief); private: + friend bool comparesEqual(const Documentation &lhs, + const Documentation &rhs) noexcept + { + return lhs.m_format == rhs.m_format && lhs.m_detailed == rhs.m_detailed + && lhs.m_brief == rhs.m_brief; + } + Q_DECLARE_EQUALITY_COMPARABLE(Documentation) + QString m_detailed; QString m_brief; Format m_format = Documentation::Native; }; -inline bool operator==(const Documentation &d1, const Documentation &d2) -{ return d1.equals(d2); } -inline bool operator!=(const Documentation &d1, const Documentation &d2) -{ return !d1.equals(d2); } - #ifndef QT_NO_DEBUG_STREAM QDebug operator<<(QDebug debug, const Documentation &); #endif diff --git a/sources/shiboken6/ApiExtractor/dotview.cpp b/sources/shiboken6/ApiExtractor/dotview.cpp index da94f5e7..0bd19225 100644 --- a/sources/shiboken6/ApiExtractor/dotview.cpp +++ b/sources/shiboken6/ApiExtractor/dotview.cpp @@ -13,7 +13,7 @@ using namespace Qt::StringLiterals; bool showDotGraph(const QString &name, const QString &graph) { - const QString imageType = u"jpg"_s; + constexpr auto imageType = "jpg"_L1; // Write out the graph to a temporary file QTemporaryFile dotFile(QDir::tempPath() + u'/' + name + u"_XXXXXX.dot"_s); @@ -43,9 +43,9 @@ bool showDotGraph(const QString &name, const QString &graph) // Launch image. Should use QDesktopServices::openUrl(), // but we don't link against QtGui #ifdef Q_OS_UNIX - const QString imageViewer = u"gwenview"_s; + constexpr auto imageViewer = "gwenview"_L1; #else - const QString imageViewer = u"mspaint"_s; + constexpr auto imageViewer = "mspaint"_L1; #endif if (!QProcess::startDetached(imageViewer, {imageFile})) { qWarning("Failed to launch viewer: %s", qPrintable(imageViewer)); diff --git a/sources/shiboken6/ApiExtractor/doxygenparser.cpp b/sources/shiboken6/ApiExtractor/doxygenparser.cpp index bcfbc39d..da790015 100644 --- a/sources/shiboken6/ApiExtractor/doxygenparser.cpp +++ b/sources/shiboken6/ApiExtractor/doxygenparser.cpp @@ -54,13 +54,12 @@ void DoxygenParser::fillDocumentation(const AbstractMetaClassPtr &metaClass) doxyFileSuffix += metaClass->name(); doxyFileSuffix += u".xml"_s; - const char* prefixes[] = { "class", "struct", "namespace" }; + static constexpr QLatin1StringView prefixes[] = { "class"_L1, "struct"_L1, "namespace"_L1 }; bool isProperty = false; QString doxyFilePath; - for (const char *prefix : prefixes) { - doxyFilePath = documentationDataDirectory() + u'/' - + QLatin1StringView(prefix) + doxyFileSuffix; + for (const auto &prefix : prefixes) { + doxyFilePath = documentationDataDirectory() + u'/' + prefix + doxyFileSuffix; if (QFile::exists(doxyFilePath)) break; doxyFilePath.clear(); @@ -151,7 +150,7 @@ void DoxygenParser::fillDocumentation(const AbstractMetaClassPtr &metaClass) } QString doc = getDocumentation(xquery, funcQuery, - DocParser::getDocModifications(metaClass, func)); + DocParser::getXpathDocModifications(func, metaClass)); if (doc.isEmpty()) { qCWarning(lcShibokenDoc, "%s", qPrintable(msgCannotFindDocumentation(doxyFilePath, func.get(), diff --git a/sources/shiboken6/ApiExtractor/enumtypeentry.h b/sources/shiboken6/ApiExtractor/enumtypeentry.h index c7fe54d0..3360d7db 100644 --- a/sources/shiboken6/ApiExtractor/enumtypeentry.h +++ b/sources/shiboken6/ApiExtractor/enumtypeentry.h @@ -37,6 +37,9 @@ public: void addEnumValueRejection(const QString &name); QStringList enumValueRejections() const; + QString docFile() const; + void setDocFile(const QString &df); + TypeEntry *clone() const override; #ifndef QT_NO_DEBUG_STREAM void formatDebug(QDebug &d) const override; diff --git a/sources/shiboken6/ApiExtractor/fileout.cpp b/sources/shiboken6/ApiExtractor/fileout.cpp index da81f5a1..6f9ec4d8 100644 --- a/sources/shiboken6/ApiExtractor/fileout.cpp +++ b/sources/shiboken6/ApiExtractor/fileout.cpp @@ -175,7 +175,7 @@ FileOut::State FileOut::done() if (!FileOut::m_dryRun) { QDir dir(info.absolutePath()); if (!dir.mkpath(dir.absolutePath())) { - const QString message = QStringLiteral("Unable to create directory '%1'") + const QString message = QString::fromLatin1("Unable to create directory '%1'") .arg(QDir::toNativeSeparators(dir.absolutePath())); throw Exception(message); } diff --git a/sources/shiboken6/ApiExtractor/functiontypeentry.h b/sources/shiboken6/ApiExtractor/functiontypeentry.h index 8042f950..53aa1fad 100644 --- a/sources/shiboken6/ApiExtractor/functiontypeentry.h +++ b/sources/shiboken6/ApiExtractor/functiontypeentry.h @@ -19,6 +19,9 @@ public: bool hasSignature(const QString& signature) const; void addSignature(const QString& signature); + QString docFile() const; + void setDocFile(const QString &df); + TypeEntry *clone() const override; #ifndef QT_NO_DEBUG_STREAM diff --git a/sources/shiboken6/ApiExtractor/include.cpp b/sources/shiboken6/ApiExtractor/include.cpp index 38f6ce74..aee6b733 100644 --- a/sources/shiboken6/ApiExtractor/include.cpp +++ b/sources/shiboken6/ApiExtractor/include.cpp @@ -24,13 +24,15 @@ QString Include::toString() const return u"import "_s + m_name + u';'; } -int Include::compare(const Include &rhs) const +Qt::strong_ordering compareThreeWay(const Include &lhs, const Include &rhs) noexcept { - if (m_type < rhs.m_type) - return -1; - if (m_type > rhs.m_type) - return 1; - return m_name.compare(rhs.m_name); + if (lhs.m_type < rhs.m_type) + return Qt::strong_ordering::less; + if (lhs.m_type > rhs.m_type) + return Qt::strong_ordering::greater; + if (auto c = lhs.m_name.compare(rhs.m_name)) + return c < 0 ? Qt::strong_ordering::less : Qt::strong_ordering::greater; + return Qt::strong_ordering::equal; } QTextStream& operator<<(QTextStream& out, const Include& g) diff --git a/sources/shiboken6/ApiExtractor/include.h b/sources/shiboken6/ApiExtractor/include.h index dcf8976c..875a941f 100644 --- a/sources/shiboken6/ApiExtractor/include.h +++ b/sources/shiboken6/ApiExtractor/include.h @@ -4,6 +4,7 @@ #ifndef INCLUDE_H #define INCLUDE_H +#include #include #include #include @@ -50,41 +51,18 @@ private: { return qHashMulti(seed, inc.m_type, inc.m_name); } + friend bool comparesEqual(const Include &lhs, const Include &rhs) noexcept + { + return lhs.m_type == rhs.m_type && lhs.m_name == rhs.m_name; + } + friend Qt::strong_ordering compareThreeWay(const Include &lhs, + const Include &rhs) noexcept; + Q_DECLARE_STRONGLY_ORDERED(Include) IncludeType m_type = IncludePath; QString m_name; }; -inline bool operator<(const Include &lhs, const Include &rhs) -{ - return lhs.compare(rhs) < 0; -} - -inline bool operator<=(const Include &lhs, const Include &rhs) -{ - return lhs.compare(rhs) <= 0; -} - -inline bool operator==(const Include &lhs, const Include &rhs) -{ - return lhs.compare(rhs) == 0; -} - -inline bool operator!=(const Include &lhs, const Include &rhs) -{ - return lhs.compare(rhs) != 0; -} - -inline bool operator>=(const Include &lhs, const Include &rhs) -{ - return lhs.compare(rhs) >= 0; -} - -inline bool operator>(const Include &lhs, const Include &rhs) -{ - return lhs.compare(rhs) > 0; -} - QTextStream& operator<<(QTextStream& out, const Include& include); TextStream& operator<<(TextStream& out, const Include& include); #ifndef QT_NO_DEBUG_STREAM diff --git a/sources/shiboken6/ApiExtractor/messages.cpp b/sources/shiboken6/ApiExtractor/messages.cpp index 766b05be..b1f0b240 100644 --- a/sources/shiboken6/ApiExtractor/messages.cpp +++ b/sources/shiboken6/ApiExtractor/messages.cpp @@ -26,6 +26,22 @@ using namespace Qt::StringLiterals; // abstractmetabuilder.cpp +static QTextStream &operator<<(QTextStream &s, Access a) +{ + switch (a) { + case Access::Public: + s << "public"; + break; + case Access::Protected: + s << "protected"; + break; + case Access::Private: + s << "private"; + break; + } + return s; +} + QString msgNoFunctionForModification(const AbstractMetaClassCPtr &klass, const QString &signature, const QString &originalSignature, @@ -301,11 +317,13 @@ QString msgSkippingFunction(const FunctionModelItem &functionItem, { QString result; QTextStream str(&result); - str << functionItem->sourceLocation() << "skipping "; - if (functionItem->isAbstract()) + str << functionItem->sourceLocation() << "skipping " + << functionItem->accessPolicy() << ' '; + const bool isAbstract = functionItem->attributes().testFlag(FunctionAttribute::Abstract); + if (isAbstract) str << "abstract "; str << "function '" << signature << "', " << why; - if (functionItem->isAbstract()) { + if (isAbstract) { str << "\nThis will lead to compilation errors due to not " "being able to instantiate the wrapper."; } @@ -338,8 +356,9 @@ QString msgSkippingField(const VariableModelItem &field, const QString &classNam { QString result; QTextStream str(&result); - str << field->sourceLocation() << "skipping field '" << className - << "::" << field->name() << "' with unmatched type '" << type << '\''; + str << field->sourceLocation() << "skipping " << field->accessPolicy() + << " field '" << className << "::" << field->name() + << "' with unmatched type '" << type << '\''; return result; } @@ -462,6 +481,21 @@ QString msgCannotFindTypeEntryForSmartPointer(const QString &t, const QString &s + u"\" for instantiation of \""_s +smartPointerType + u"\"."_s; } +QString msgInheritTemplateIssue(const AbstractMetaClassPtr &subclass, + const TypeInfo &info, + const QString &what) +{ + return "While inheriting template "_L1 + subclass->name() + + " from "_L1 + info.toString() + ": "_L1 + what; +} + +QString msgIgnoringTemplateParameter(const QString &typeName, + const char *why) +{ + return "Ignoring template parameter "_L1 + typeName + + ": "_L1 + QLatin1StringView(why); +} + QString msgInvalidSmartPointerType(const TypeInfo &i) { return u"Invalid smart pointer type \""_s +i.toString() + u"\"."_s; @@ -597,9 +631,10 @@ QString msgCannotFindDocumentation(const QString &fileName, const AbstractMetaEnum &e, const QString &query) { - return msgCannotFindDocumentation(fileName, "enum", - metaClass->name() + u"::"_s + e.name(), - query); + QString name = e.name(); + if (metaClass != nullptr) + name.prepend(metaClass->name() + "::"_L1); + return msgCannotFindDocumentation(fileName, "enum", name, query); } QString msgCannotFindDocumentation(const QString &fileName, @@ -607,9 +642,10 @@ QString msgCannotFindDocumentation(const QString &fileName, const AbstractMetaField &f, const QString &query) { - return msgCannotFindDocumentation(fileName, "field", - metaClass->name() + u"::"_s + f.name(), - query); + QString name = f.name(); + if (metaClass != nullptr) + name.prepend(metaClass->name() + "::"_L1); + return msgCannotFindDocumentation(fileName, "field", name, query); } QString msgXpathDocModificationError(const DocModificationList& mods, @@ -637,13 +673,13 @@ QString msgXpathDocModificationError(const DocModificationList& mods, QString msgCannotOpenForReading(const QFile &f) { - return QStringLiteral("Failed to open file '%1' for reading: %2") + return QString::fromLatin1("Failed to open file '%1' for reading: %2") .arg(QDir::toNativeSeparators(f.fileName()), f.errorString()); } QString msgCannotOpenForWriting(const QFile &f) { - return QStringLiteral("Failed to open file '%1' for writing: %2") + return QString::fromLatin1("Failed to open file '%1' for writing: %2") .arg(QDir::toNativeSeparators(f.fileName()), f.errorString()); } diff --git a/sources/shiboken6/ApiExtractor/messages.h b/sources/shiboken6/ApiExtractor/messages.h index 2899cbdf..e3f582b4 100644 --- a/sources/shiboken6/ApiExtractor/messages.h +++ b/sources/shiboken6/ApiExtractor/messages.h @@ -126,6 +126,10 @@ QString msgUnableToTranslateType(const TypeInfo &typeInfo, QString msgCannotFindTypeEntry(const QString &t); QString msgCannotFindTypeEntryForSmartPointer(const QString &t, const QString &smartPointerType); +QString msgInheritTemplateIssue(const AbstractMetaClassPtr &subclass, + const TypeInfo &info, const QString &what); +QString msgIgnoringTemplateParameter(const QString &typeName, + const char *why); QString msgInvalidSmartPointerType(const TypeInfo &i); QString msgCannotFindSmartPointerInstantion(const TypeInfo &i); diff --git a/sources/shiboken6/ApiExtractor/modifications.cpp b/sources/shiboken6/ApiExtractor/modifications.cpp index 4e08d856..d876e803 100644 --- a/sources/shiboken6/ApiExtractor/modifications.cpp +++ b/sources/shiboken6/ApiExtractor/modifications.cpp @@ -14,20 +14,6 @@ using namespace Qt::StringLiterals; -// ---------------------- Modification -QString FunctionModification::accessModifierString() const -{ - if (isPrivate()) - return u"private"_s; - if (isProtected()) - return u"protected"_s; - if (isPublic()) - return u"public"_s; - if (isFriendly()) - return u"friendly"_s; - return {}; -} - // ---------------------- FieldModification class FieldModificationData : public QSharedData diff --git a/sources/shiboken6/ApiExtractor/modifications.h b/sources/shiboken6/ApiExtractor/modifications.h index 3763f615..27a38f1a 100644 --- a/sources/shiboken6/ApiExtractor/modifications.h +++ b/sources/shiboken6/ApiExtractor/modifications.h @@ -141,8 +141,7 @@ public: enum ModifierFlag { Private = 0x0001, Protected = 0x0002, - Public = 0x0003, - Friendly = 0x0004, + Public = 0x0004, AccessModifierMask = 0x000f, Final = 0x0010, @@ -191,10 +190,6 @@ public: { return accessModifier() == Public; } - bool isFriendly() const - { - return accessModifier() == Friendly; - } bool isFinal() const { return modifiers().testFlag(Final); @@ -203,7 +198,6 @@ public: { return modifiers().testFlag(NonFinal); } - QString accessModifierString() const; bool isDeprecated() const { diff --git a/sources/shiboken6/ApiExtractor/parser/codemodel.cpp b/sources/shiboken6/ApiExtractor/parser/codemodel.cpp index cf8403d5..259a706d 100644 --- a/sources/shiboken6/ApiExtractor/parser/codemodel.cpp +++ b/sources/shiboken6/ApiExtractor/parser/codemodel.cpp @@ -13,27 +13,17 @@ #include #include -#include -#include using namespace Qt::StringLiterals; -// Predicate to find an item by name in a list of std::shared_ptr -template class ModelItemNamePredicate -{ -public: - explicit ModelItemNamePredicate(const QString &name) : m_name(name) {} - bool operator()(const std::shared_ptr &item) const { return item->name() == m_name; } - -private: - const QString m_name; -}; - template -static std::shared_ptr findModelItem(const QList > &list, const QString &name) +static std::shared_ptr findModelItem(const QList > &list, + QAnyStringView name) { - const auto it = std::find_if(list.cbegin(), list.cend(), ModelItemNamePredicate(name)); - return it != list.cend() ? *it : std::shared_ptr(); + using ItemPtr = std::shared_ptr; + auto pred = [name](const ItemPtr &item) { return item->name() == name; }; + const auto it = std::find_if(list.cbegin(), list.cend(), pred); + return it != list.cend() ? *it : ItemPtr{}; } // --------------------------------------------------------------------------- @@ -54,7 +44,7 @@ void CodeModel::addFile(const FileModelItem &item) m_files.append(item); } -FileModelItem CodeModel::findFile(const QString &name) const +FileModelItem CodeModel::findFile(QAnyStringView name) const { return findModelItem(m_files, name); } @@ -628,52 +618,46 @@ void _ScopeModelItem::formatDebug(QDebug &d) const } #endif // !QT_NO_DEBUG_STREAM -namespace { // Predicate to match a non-template class name against the class list. // "Vector" should match "Vector" as well as "Vector" (as seen for methods // from within the class "Vector"). -class ClassNamePredicate +static bool matchClassNameNonTemplatePart(const ClassModelItem &item, const QString &name) { -public: - explicit ClassNamePredicate(const QString &name) : m_name(name) {} - bool operator()(const ClassModelItem &item) const - { - const QString &itemName = item->name(); - if (!itemName.startsWith(m_name)) - return false; - return itemName.size() == m_name.size() || itemName.at(m_name.size()) == u'<'; - } - -private: - const QString m_name; -}; -} // namespace + const QString &itemName = item->name(); + if (!itemName.startsWith(name)) + return false; + return itemName.size() == name.size() || itemName.at(name.size()) == u'<'; +} ClassModelItem _ScopeModelItem::findClass(const QString &name) const { // A fully qualified template is matched by name only const ClassList::const_iterator it = name.contains(u'<') - ? std::find_if(m_classes.begin(), m_classes.end(), ModelItemNamePredicate<_ClassModelItem>(name)) - : std::find_if(m_classes.begin(), m_classes.end(), ClassNamePredicate(name)); + ? std::find_if(m_classes.begin(), m_classes.end(), + [&name](const ClassModelItem &item) { + return item->name() == name; }) + : std::find_if(m_classes.begin(), m_classes.end(), + [&name](const ClassModelItem &item) { + return matchClassNameNonTemplatePart(item, name); }); return it != m_classes.end() ? *it : ClassModelItem(); } -VariableModelItem _ScopeModelItem::findVariable(const QString &name) const +VariableModelItem _ScopeModelItem::findVariable(QAnyStringView name) const { return findModelItem(m_variables, name); } -TypeDefModelItem _ScopeModelItem::findTypeDef(const QString &name) const +TypeDefModelItem _ScopeModelItem::findTypeDef(QAnyStringView name) const { return findModelItem(m_typeDefs, name); } -TemplateTypeAliasModelItem _ScopeModelItem::findTemplateTypeAlias(const QString &name) const +TemplateTypeAliasModelItem _ScopeModelItem::findTemplateTypeAlias(QAnyStringView name) const { return findModelItem(m_templateTypeAliases, name); } -EnumModelItem _ScopeModelItem::findEnum(const QString &name) const +EnumModelItem _ScopeModelItem::findEnum(QAnyStringView name) const { return findModelItem(m_enums, name); } @@ -762,7 +746,7 @@ _ScopeModelItem::FindEnumByValueReturn return findEnumByValueRecursion(this, value, enumValue); } -FunctionList _ScopeModelItem::findFunctions(const QString &name) const +FunctionList _ScopeModelItem::findFunctions(QAnyStringView name) const { FunctionList result; for (const FunctionModelItem &func : m_functions) { @@ -791,7 +775,7 @@ void _NamespaceModelItem::addNamespace(NamespaceModelItem item) m_namespaces.append(item); } -NamespaceModelItem _NamespaceModelItem::findNamespace(const QString &name) const +NamespaceModelItem _NamespaceModelItem::findNamespace(QAnyStringView name) const { return findModelItem(m_namespaces, name); } @@ -1018,66 +1002,16 @@ void _FunctionModelItem::setDeleted(bool d) m_isDeleted = d; } -bool _FunctionModelItem::isDeprecated() const -{ - return m_isDeprecated; -} - -void _FunctionModelItem::setDeprecated(bool d) -{ - m_isDeprecated = d; -} - -bool _FunctionModelItem::isVirtual() const -{ - return m_isVirtual; -} - -void _FunctionModelItem::setVirtual(bool isVirtual) -{ - m_isVirtual = isVirtual; -} - bool _FunctionModelItem::isInline() const { return m_isInline; } -bool _FunctionModelItem::isOverride() const -{ - return m_isOverride; -} - -void _FunctionModelItem::setOverride(bool o) -{ - m_isOverride = o; -} - -bool _FunctionModelItem::isFinal() const -{ - return m_isFinal; -} - -void _FunctionModelItem::setFinal(bool f) -{ - m_isFinal = f; -} - void _FunctionModelItem::setInline(bool isInline) { m_isInline = isInline; } -bool _FunctionModelItem::isExplicit() const -{ - return m_isExplicit; -} - -void _FunctionModelItem::setExplicit(bool isExplicit) -{ - m_isExplicit = isExplicit; -} - bool _FunctionModelItem::isHiddenFriend() const { return m_isHiddenFriend; @@ -1088,27 +1022,6 @@ void _FunctionModelItem::setHiddenFriend(bool f) m_isHiddenFriend = f; } -bool _FunctionModelItem::isAbstract() const -{ - return m_isAbstract; -} - -void _FunctionModelItem::setAbstract(bool isAbstract) -{ - m_isAbstract = isAbstract; -} - -// Qt -bool _FunctionModelItem::isInvokable() const -{ - return m_isInvokable; -} - -void _FunctionModelItem::setInvokable(bool isInvokable) -{ - m_isInvokable = isInvokable; -} - QString _FunctionModelItem::typeSystemSignature() const // For dumping out type system files { QString result; @@ -1233,17 +1146,17 @@ void _FunctionModelItem::formatDebug(QDebug &d) const d << " [deleted!]"; if (m_isInline) d << " [inline]"; - if (m_isVirtual) + if (m_attributes.testFlag(FunctionAttribute::Virtual)) d << " [virtual]"; - if (m_isOverride) + if (m_attributes.testFlag(FunctionAttribute::Override)) d << " [override]"; - if (m_isDeprecated) + if (m_attributes.testFlag(FunctionAttribute::Deprecated)) d << " [deprecated]"; - if (m_isFinal) + if (m_attributes.testFlag(FunctionAttribute::Final)) d << " [final]"; - if (m_isAbstract) + if (m_attributes.testFlag(FunctionAttribute::Abstract)) d << " [abstract]"; - if (m_isExplicit) + if (m_attributes.testFlag(FunctionAttribute::Explicit)) d << " [explicit]"; if (m_isInvokable) d << " [invokable]"; diff --git a/sources/shiboken6/ApiExtractor/parser/codemodel.h b/sources/shiboken6/ApiExtractor/parser/codemodel.h index 3ebc44c2..b31c0916 100644 --- a/sources/shiboken6/ApiExtractor/parser/codemodel.h +++ b/sources/shiboken6/ApiExtractor/parser/codemodel.h @@ -74,7 +74,7 @@ public: NamespaceModelItem globalNamespace() const; void addFile(const FileModelItem &item); - FileModelItem findFile(const QString &name) const; + FileModelItem findFile(QAnyStringView name) const; static CodeModelItem findItem(const QStringList &qualifiedName, const ScopeModelItem &scope); @@ -196,7 +196,7 @@ public: void addVariable(const VariableModelItem &item); ClassModelItem findClass(const QString &name) const; - EnumModelItem findEnum(const QString &name) const; + EnumModelItem findEnum(QAnyStringView name) const; struct FindEnumByValueReturn { @@ -207,10 +207,10 @@ public: }; FindEnumByValueReturn findEnumByValue(QStringView value) const; - FunctionList findFunctions(const QString &name) const; - TypeDefModelItem findTypeDef(const QString &name) const; - TemplateTypeAliasModelItem findTemplateTypeAlias(const QString &name) const; - VariableModelItem findVariable(const QString &name) const; + FunctionList findFunctions(QAnyStringView name) const; + TypeDefModelItem findTypeDef(QAnyStringView name) const; + TemplateTypeAliasModelItem findTemplateTypeAlias(QAnyStringView name) const; + VariableModelItem findVariable(QAnyStringView name) const; void addEnumsDeclaration(const QString &enumsDeclaration); QStringList enumsDeclarations() const { return m_enumsDeclarations; } @@ -338,7 +338,7 @@ public: void addNamespace(NamespaceModelItem item); - NamespaceModelItem findNamespace(const QString &name) const; + NamespaceModelItem findNamespace(QAnyStringView name) const; void appendNamespace(const _NamespaceModelItem &other); @@ -485,36 +485,19 @@ public: static std::optional functionTypeFromName(QStringView name); + FunctionAttributes attributes() const { return m_attributes; } + void setAttributes(FunctionAttributes a) { m_attributes = a; } + void setAttribute(FunctionAttribute a, bool on = true) { m_attributes.setFlag(a, on); } + bool isDeleted() const; void setDeleted(bool d); - bool isDeprecated() const; - void setDeprecated(bool d); - - bool isVirtual() const; - void setVirtual(bool isVirtual); - - bool isOverride() const; - void setOverride(bool o); - - bool isFinal() const; - void setFinal(bool f); - bool isInline() const; void setInline(bool isInline); - bool isExplicit() const; - void setExplicit(bool isExplicit); - bool isHiddenFriend() const; void setHiddenFriend(bool f); - bool isInvokable() const; // Qt - void setInvokable(bool isInvokable); // Qt - - bool isAbstract() const; - void setAbstract(bool isAbstract); - bool isVariadics() const; void setVariadics(bool isVariadics); @@ -546,17 +529,12 @@ private: CodeModel::FunctionType _determineTypeHelper() const; ArgumentList m_arguments; + FunctionAttributes m_attributes; CodeModel::FunctionType m_functionType = CodeModel::Normal; union { struct { uint m_isDeleted: 1; - uint m_isVirtual: 1; - uint m_isOverride: 1; - uint m_isFinal: 1; - uint m_isDeprecated: 1; uint m_isInline: 1; - uint m_isAbstract: 1; - uint m_isExplicit: 1; uint m_isVariadics: 1; uint m_isHiddenFriend: 1; uint m_isInvokable : 1; // Qt diff --git a/sources/shiboken6/ApiExtractor/parser/codemodel_enums.h b/sources/shiboken6/ApiExtractor/parser/codemodel_enums.h index 1a058bdf..e5c429bd 100644 --- a/sources/shiboken6/ApiExtractor/parser/codemodel_enums.h +++ b/sources/shiboken6/ApiExtractor/parser/codemodel_enums.h @@ -4,6 +4,8 @@ #ifndef CODEMODEL_ENUMS_H #define CODEMODEL_ENUMS_H +#include + enum ReferenceType { NoReference, LValueReference, @@ -43,4 +45,17 @@ enum class Access Public }; +enum class FunctionAttribute { + Abstract = 0x00000001, + Static = 0x00000002, + Virtual = 0x00000004, + Override = 0x00000008, + Final = 0x00000010, + Deprecated = 0x00000020, // Code annotation + Explicit = 0x00000040, // Constructor +}; + +Q_DECLARE_FLAGS(FunctionAttributes, FunctionAttribute) +Q_DECLARE_OPERATORS_FOR_FLAGS(FunctionAttributes) + #endif // CODEMODEL_ENUMS_H diff --git a/sources/shiboken6/ApiExtractor/parser/enumvalue.cpp b/sources/shiboken6/ApiExtractor/parser/enumvalue.cpp index 09c34cb9..3749e16a 100644 --- a/sources/shiboken6/ApiExtractor/parser/enumvalue.cpp +++ b/sources/shiboken6/ApiExtractor/parser/enumvalue.cpp @@ -56,11 +56,12 @@ EnumValue EnumValue::toUnsigned() const return result; } -bool EnumValue::equals(const EnumValue &rhs) const +bool comparesEqual(const EnumValue &lhs, const EnumValue &rhs) noexcept { - if (m_type != rhs.m_type) + if (lhs.m_type != rhs.m_type) return false; - return m_type == Signed ? m_value == rhs.m_value : m_unsignedValue == rhs.m_unsignedValue; + return lhs.m_type == EnumValue::Signed + ? lhs.m_value == rhs.m_value : lhs.m_unsignedValue == rhs.m_unsignedValue; } void EnumValue::formatDebugHex(QDebug &d) const diff --git a/sources/shiboken6/ApiExtractor/parser/enumvalue.h b/sources/shiboken6/ApiExtractor/parser/enumvalue.h index 053a97a0..bbd5a712 100644 --- a/sources/shiboken6/ApiExtractor/parser/enumvalue.h +++ b/sources/shiboken6/ApiExtractor/parser/enumvalue.h @@ -4,7 +4,9 @@ #ifndef ENUMVALUE_H #define ENUMVALUE_H -#include +#include +#include +#include QT_FORWARD_DECLARE_CLASS(QDebug) QT_FORWARD_DECLARE_CLASS(QString) @@ -39,6 +41,10 @@ public: void formatDebugHex(QDebug &d) const; private: + friend bool comparesEqual(const EnumValue &lhs, + const EnumValue &rhs) noexcept; + Q_DECLARE_EQUALITY_COMPARABLE(EnumValue) + #ifndef QT_NO_DEBUG_STREAM friend QDebug operator<<(QDebug, const EnumValue &); #endif @@ -52,9 +58,4 @@ private: Type m_type = Signed; }; -inline bool operator==(const EnumValue &e1, const EnumValue &e2) -{ return e1.equals(e2); } -inline bool operator!=(const EnumValue &e1, const EnumValue &e2) -{ return !e1.equals(e2); } - #endif // ENUMVALUE_H diff --git a/sources/shiboken6/ApiExtractor/parser/typeinfo.cpp b/sources/shiboken6/ApiExtractor/parser/typeinfo.cpp index 5fa9374e..f8c5c31d 100644 --- a/sources/shiboken6/ApiExtractor/parser/typeinfo.cpp +++ b/sources/shiboken6/ApiExtractor/parser/typeinfo.cpp @@ -266,6 +266,12 @@ void TypeInfo::clearInstantiations() d->m_instantiations.clear(); } +bool TypeInfo::isPlain() const +{ + return d->m_constant == 0 && d->m_volatile == 0 && d->m_referenceType == NoReference + && d->m_indirections.isEmpty() && d->m_arrayElements.isEmpty(); +} + TypeInfo TypeInfo::resolveType(TypeInfo const &__type, const ScopeModelItem &__scope) { CodeModel *__model = __scope->model(); @@ -446,31 +452,28 @@ bool TypeInfoData::equals(const TypeInfoData &other) const && m_instantiations == other.m_instantiations; } -bool TypeInfo::equals(const TypeInfo &other) const + +bool comparesEqual(const TypeInfo &lhs, const TypeInfo &rhs) noexcept { - return d.data() == other.d.data() || d->equals(*other.d); + return lhs.d.data() == rhs.d.data() || lhs.d->equals(*rhs.d); } QString TypeInfo::indirectionKeyword(Indirection i) { - return i == Indirection::Pointer - ? QStringLiteral("*") : QStringLiteral("*const"); + return i == Indirection::Pointer ? "*"_L1 : "*const"_L1; } -static inline QString constQualifier() { return QStringLiteral("const"); } -static inline QString volatileQualifier() { return QStringLiteral("volatile"); } - bool TypeInfo::stripLeadingConst(QString *s) { - return stripLeadingQualifier(constQualifier(), s); + return stripLeadingQualifier("const"_L1, s); } bool TypeInfo::stripLeadingVolatile(QString *s) { - return stripLeadingQualifier(volatileQualifier(), s); + return stripLeadingQualifier("volatile"_L1, s); } -bool TypeInfo::stripLeadingQualifier(const QString &qualifier, QString *s) +bool TypeInfo::stripLeadingQualifier(QLatin1StringView qualifier, QString *s) { // "const int x" const auto qualifierSize = qualifier.size(); diff --git a/sources/shiboken6/ApiExtractor/parser/typeinfo.h b/sources/shiboken6/ApiExtractor/parser/typeinfo.h index 0f1a04f6..e4f363b6 100644 --- a/sources/shiboken6/ApiExtractor/parser/typeinfo.h +++ b/sources/shiboken6/ApiExtractor/parser/typeinfo.h @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -79,13 +80,13 @@ public: void addInstantiation(const TypeInfo &i); void clearInstantiations(); + bool isPlain() const; // neither const,volatile, no indirections/references, array + bool isStdType() const; std::pair parseTemplateArgumentList(const QString &l, qsizetype from = 0); - bool equals(const TypeInfo &other) const; - // ### arrays and templates?? QString toString() const; @@ -103,12 +104,16 @@ public: static bool stripLeadingConst(QString *s); static bool stripLeadingVolatile(QString *s); - static bool stripLeadingQualifier(const QString &qualifier, QString *s); + static bool stripLeadingQualifier(QLatin1StringView qualifier, QString *s); static void stripQualifiers(QString *s); void simplifyStdType(); private: + friend bool comparesEqual(const TypeInfo &lhs, + const TypeInfo &rhs) noexcept; + Q_DECLARE_EQUALITY_COMPARABLE(TypeInfo) + QSharedDataPointer d; friend class TypeInfoTemplateArgumentHandler; @@ -116,12 +121,6 @@ private: static TypeInfo resolveType(CodeModelItem item, TypeInfo const &__type, const ScopeModelItem &__scope); }; -inline bool operator==(const TypeInfo &t1, const TypeInfo &t2) -{ return t1.equals(t2); } - -inline bool operator!=(const TypeInfo &t1, const TypeInfo &t2) -{ return !t1.equals(t2); } - #ifndef QT_NO_DEBUG_STREAM QDebug operator<<(QDebug d, const TypeInfo &t); #endif diff --git a/sources/shiboken6/ApiExtractor/predefined_templates.cpp b/sources/shiboken6/ApiExtractor/predefined_templates.cpp index 7ff7c26e..992f735a 100644 --- a/sources/shiboken6/ApiExtractor/predefined_templates.cpp +++ b/sources/shiboken6/ApiExtractor/predefined_templates.cpp @@ -56,20 +56,20 @@ for (auto oit = std::begin(%out), oend = std::end(%out); oit != oend; ++oit) { )"_s; } -static const char stlMapKeyAccessor[] = "->first"; -static const char stlMapValueAccessor[] = "->second"; -static const char qtMapKeyAccessor[] = ".key()"; -static const char qtMapValueAccessor[] = ".value()"; +static constexpr auto stlMapKeyAccessor = "->first"_L1; +static constexpr auto stlMapValueAccessor = "->second"_L1; +static constexpr auto qtMapKeyAccessor = ".key()"_L1; +static constexpr auto qtMapValueAccessor = ".value()"_L1; static QString cppMapToPyDict(bool isQMap) { return uR"(PyObject *%out = PyDict_New(); for (auto it = std::cbegin(%in), end = std::cend(%in); it != end; ++it) { const auto &key = it)"_s - + QLatin1StringView(isQMap ? qtMapKeyAccessor : stlMapKeyAccessor) + + (isQMap ? qtMapKeyAccessor : stlMapKeyAccessor) + uR"(; const auto &value = it)"_s - + QLatin1StringView(isQMap ? qtMapValueAccessor : stlMapValueAccessor) + + (isQMap ? qtMapValueAccessor : stlMapValueAccessor) + uR"(; PyObject *pyKey = %CONVERTTOPYTHON[%INTYPE_0](key); PyObject *pyValue = %CONVERTTOPYTHON[%INTYPE_1](value); @@ -103,7 +103,7 @@ static QString cppMultiMapToPyDict(bool isQMultiMap) return uR"(PyObject *%out = PyDict_New(); for (auto it = std::cbegin(%in), end = std::cend(%in); it != end; ) { const auto &key = it)"_s - + QLatin1StringView(isQMultiMap ? qtMapKeyAccessor : stlMapKeyAccessor) + + (isQMultiMap ? qtMapKeyAccessor : stlMapKeyAccessor) + uR"(; PyObject *pyKey = %CONVERTTOPYTHON[%INTYPE_0](key); auto upper = %in.)"_s @@ -129,7 +129,7 @@ static QString cppMultiHashToPyDict(bool isQMultiHash) return uR"(PyObject *%out = PyDict_New(); for (auto it = std::cbegin(%in), end = std::cend(%in); it != end; ) { const auto &key = it)"_s - + QLatin1StringView(isQMultiHash ? qtMapKeyAccessor : stlMapKeyAccessor) + + (isQMultiHash ? qtMapKeyAccessor : stlMapKeyAccessor) + uR"(; PyObject *pyKey = %CONVERTTOPYTHON[%INTYPE_0](key); auto range = %in.equal_range(key); diff --git a/sources/shiboken6/ApiExtractor/propertyspec.cpp b/sources/shiboken6/ApiExtractor/propertyspec.cpp index c676d3cf..34cdd74a 100644 --- a/sources/shiboken6/ApiExtractor/propertyspec.cpp +++ b/sources/shiboken6/ApiExtractor/propertyspec.cpp @@ -192,11 +192,11 @@ TypeSystemProperty QPropertySpec::typeSystemPropertyFromQ_Property(const QString enum class PropertyToken { None, Read, Write, Designable, Reset, Notify }; static const QHash tokenLookup = { - {QStringLiteral("READ"), PropertyToken::Read}, - {QStringLiteral("WRITE"), PropertyToken::Write}, - {QStringLiteral("DESIGNABLE"), PropertyToken::Designable}, - {QStringLiteral("RESET"), PropertyToken::Reset}, - {QStringLiteral("NOTIFY"), PropertyToken::Notify} + {"READ"_L1, PropertyToken::Read}, + {"WRITE"_L1, PropertyToken::Write}, + {"DESIGNABLE"_L1, PropertyToken::Designable}, + {"RESET"_L1, PropertyToken::Reset}, + {"NOTIFY"_L1, PropertyToken::Notify} }; errorMessage->clear(); diff --git a/sources/shiboken6/ApiExtractor/pymethoddefentry.h b/sources/shiboken6/ApiExtractor/pymethoddefentry.h index 4a716ba5..a8694eb3 100644 --- a/sources/shiboken6/ApiExtractor/pymethoddefentry.h +++ b/sources/shiboken6/ApiExtractor/pymethoddefentry.h @@ -13,10 +13,10 @@ class TextStream; struct castToPyCFunction { - explicit castToPyCFunction(QStringView function) noexcept : + explicit castToPyCFunction(QAnyStringView function) noexcept : m_function(function) {} - QStringView m_function; + QAnyStringView m_function; }; struct PyMethodDefEntry diff --git a/sources/shiboken6/ApiExtractor/qtcompat.h b/sources/shiboken6/ApiExtractor/qtcompat.h index 9627bb76..3837dcfd 100644 --- a/sources/shiboken6/ApiExtractor/qtcompat.h +++ b/sources/shiboken6/ApiExtractor/qtcompat.h @@ -4,7 +4,7 @@ #ifndef QTCOMPAT_H #define QTCOMPAT_H -#include +#include #if QT_VERSION < 0x060400 diff --git a/sources/shiboken6/ApiExtractor/qtdocparser.cpp b/sources/shiboken6/ApiExtractor/qtdocparser.cpp index badf6b7a..5bd99bbd 100644 --- a/sources/shiboken6/ApiExtractor/qtdocparser.cpp +++ b/sources/shiboken6/ApiExtractor/qtdocparser.cpp @@ -15,6 +15,8 @@ #include "reporthandler.h" #include "flagstypeentry.h" #include "complextypeentry.h" +#include "functiontypeentry.h" +#include "enumtypeentry.h" #include "qtcompat.h" @@ -26,8 +28,9 @@ using namespace Qt::StringLiterals; enum { debugFunctionSearch = 0 }; -static inline QString briefStartElement() { return QStringLiteral(""); } -static inline QString briefEndElement() { return QStringLiteral(""); } +constexpr auto briefStartElement = ""_L1; +constexpr auto briefEndElement = ""_L1; +constexpr auto webxmlSuffix = ".webxml"_L1; Documentation QtDocParser::retrieveModuleDocumentation() { @@ -112,7 +115,7 @@ QString QtDocParser::functionDocumentation(const QString &sourceFileName, queryFunctionDocumentation(sourceFileName, classDocumentation, metaClass, func, errorMessage); - const auto funcModifs = DocParser::getDocModifications(metaClass, func); + const auto funcModifs = DocParser::getXpathDocModifications(func, metaClass); return docString.isEmpty() || funcModifs.isEmpty() ? docString : applyDocModifications(funcModifs, docString); } @@ -187,21 +190,92 @@ QString QtDocParser::queryFunctionDocumentation(const QString &sourceFileName, // from the source. static QString extractBrief(QString *value) { - const auto briefStart = value->indexOf(briefStartElement()); + const auto briefStart = value->indexOf(briefStartElement); if (briefStart < 0) return {}; - const auto briefEnd = value->indexOf(briefEndElement(), - briefStart + briefStartElement().size()); + const auto briefEnd = value->indexOf(briefEndElement, + briefStart + briefStartElement.size()); if (briefEnd < briefStart) return {}; - const auto briefLength = briefEnd + briefEndElement().size() - briefStart; + const auto briefLength = briefEnd + briefEndElement.size() - briefStart; QString briefValue = value->mid(briefStart, briefLength); - briefValue.insert(briefValue.size() - briefEndElement().size(), + briefValue.insert(briefValue.size() - briefEndElement.size(), u" More_..."_s); value->remove(briefStart, briefLength); return briefValue; } +// Find the webxml file for global functions/enums +// by the doc-file typesystem attribute or via include file. +static QString findGlobalWebXmLFile(const QString &documentationDataDirectory, + const QString &docFile, + const Include &include) +{ + QString result; + if (!docFile.isEmpty()) { + result = documentationDataDirectory + u'/' + docFile; + if (!result.endsWith(webxmlSuffix)) + result += webxmlSuffix; + return QFileInfo::exists(result) ? result : QString{}; + } + if (include.name().isEmpty()) + return {}; + // qdoc "\headerfile " directive produces "qtlogging.webxml" + result = documentationDataDirectory + u'/' + + QFileInfo(include.name()).baseName() + webxmlSuffix; + if (QFileInfo::exists(result)) + return result; + // qdoc "\headerfile " produces "qdrawutil-h.webxml" + result.insert(result.size() - webxmlSuffix.size(), "-h"_L1); + return QFileInfo::exists(result) ? result : QString{}; +} + +void QtDocParser::fillGlobalFunctionDocumentation(const AbstractMetaFunctionPtr &f) +{ + auto te = f->typeEntry(); + if (te == nullptr) + return; + + const QString sourceFileName = + findGlobalWebXmLFile(documentationDataDirectory(), te->docFile(), te->include()); + if (sourceFileName.isEmpty()) + return; + + QString errorMessage; + auto classDocumentationO = parseWebXml(sourceFileName, &errorMessage); + if (!classDocumentationO.has_value()) { + qCWarning(lcShibokenDoc, "%s", qPrintable(errorMessage)); + return; + } + const QString detailed = + functionDocumentation(sourceFileName, classDocumentationO.value(), + {}, f, &errorMessage); + if (!errorMessage.isEmpty()) + qCWarning(lcShibokenDoc, "%s", qPrintable(errorMessage)); + const Documentation documentation(detailed, {}); + f->setDocumentation(documentation); +} + +void QtDocParser::fillGlobalEnumDocumentation(AbstractMetaEnum &e) +{ + auto te = e.typeEntry(); + const QString sourceFileName = + findGlobalWebXmLFile(documentationDataDirectory(), te->docFile(), te->include()); + if (sourceFileName.isEmpty()) + return; + + QString errorMessage; + auto classDocumentationO = parseWebXml(sourceFileName, &errorMessage); + if (!classDocumentationO.has_value()) { + qCWarning(lcShibokenDoc, "%s", qPrintable(errorMessage)); + return; + } + if (!extractEnumDocumentation(classDocumentationO.value(), e)) { + qCWarning(lcShibokenDoc, "%s", + qPrintable(msgCannotFindDocumentation(sourceFileName, {}, e, {}))); + } +} + void QtDocParser::fillDocumentation(const AbstractMetaClassPtr &metaClass) { if (!metaClass) @@ -218,9 +292,9 @@ void QtDocParser::fillDocumentation(const AbstractMetaClassPtr &metaClass) + metaClass->qualifiedCppName().toLower(); sourceFileRoot.replace(u"::"_s, u"-"_s); - QFileInfo sourceFile(sourceFileRoot + QStringLiteral(".webxml")); + QFileInfo sourceFile(sourceFileRoot + webxmlSuffix); if (!sourceFile.exists()) - sourceFile.setFile(sourceFileRoot + QStringLiteral(".xml")); + sourceFile.setFile(sourceFileRoot + ".xml"_L1); if (!sourceFile.exists()) { qCWarning(lcShibokenDoc).noquote().nospace() << "Can't find qdoc file for class " << metaClass->name() << ", tried: " @@ -231,18 +305,19 @@ void QtDocParser::fillDocumentation(const AbstractMetaClassPtr &metaClass) const QString sourceFileName = sourceFile.absoluteFilePath(); QString errorMessage; - const ClassDocumentation classDocumentation = parseWebXml(sourceFileName, &errorMessage); - if (!classDocumentation) { + const auto classDocumentationO = parseWebXml(sourceFileName, &errorMessage); + if (!classDocumentationO.has_value()) { qCWarning(lcShibokenDoc, "%s", qPrintable(errorMessage)); return; } + const auto &classDocumentation = classDocumentationO.value(); for (const auto &p : classDocumentation.properties) { Documentation doc(p.description, p.brief); metaClass->setPropertyDocumentation(p.name, doc); } - QString docString = applyDocModifications(metaClass->typeEntry()->docModifications(), + QString docString = applyDocModifications(DocParser::getXpathDocModifications(metaClass), classDocumentation.description); if (docString.isEmpty()) { @@ -283,28 +358,35 @@ void QtDocParser::fillDocumentation(const AbstractMetaClassPtr &metaClass) #endif // Enums for (AbstractMetaEnum &meta_enum : metaClass->enums()) { - Documentation enumDoc; - const auto index = classDocumentation.indexOfEnum(meta_enum.name()); - if (index != -1) { - QString doc = classDocumentation.enums.at(index).description; - const auto firstPara = doc.indexOf(u""); - if (firstPara != -1) { - const QString baseClass = QtDocParser::enumBaseClass(meta_enum); - if (baseClass != u"Enum") { - const QString note = u"(inherits enum."_s + baseClass - + u") "_s; - doc.insert(firstPara + 6, note); - } - } - enumDoc.setValue(doc); - meta_enum.setDocumentation(enumDoc); - } else { + if (!extractEnumDocumentation(classDocumentation, meta_enum)) { qCWarning(lcShibokenDoc, "%s", qPrintable(msgCannotFindDocumentation(sourceFileName, metaClass, meta_enum, {}))); } } } +bool QtDocParser::extractEnumDocumentation(const ClassDocumentation &classDocumentation, + AbstractMetaEnum &meta_enum) +{ + Documentation enumDoc; + const auto index = classDocumentation.indexOfEnum(meta_enum.name()); + if (index == -1) + return false; + QString doc = classDocumentation.enums.at(index).description; + const auto firstPara = doc.indexOf(u""); + if (firstPara != -1) { + const QString baseClass = QtDocParser::enumBaseClass(meta_enum); + if (baseClass != "Enum"_L1) { + const QString note = "(inherits enum."_L1 + baseClass + + ") "_L1; + doc.insert(firstPara + 6, note); + } + } + enumDoc.setValue(doc); + meta_enum.setDocumentation(enumDoc); + return true; +} + static QString qmlReferenceLink(const QFileInfo &qmlModuleFi) { QString result; diff --git a/sources/shiboken6/ApiExtractor/qtdocparser.h b/sources/shiboken6/ApiExtractor/qtdocparser.h index 2ad94197..f6ba5e47 100644 --- a/sources/shiboken6/ApiExtractor/qtdocparser.h +++ b/sources/shiboken6/ApiExtractor/qtdocparser.h @@ -13,6 +13,9 @@ class QtDocParser : public DocParser public: QtDocParser() = default; void fillDocumentation(const AbstractMetaClassPtr &metaClass) override; + void fillGlobalFunctionDocumentation(const AbstractMetaFunctionPtr &f) override; + void fillGlobalEnumDocumentation(AbstractMetaEnum &e) override; + Documentation retrieveModuleDocumentation() override; Documentation retrieveModuleDocumentation(const QString& name) override; @@ -28,6 +31,9 @@ private: const AbstractMetaClassCPtr &metaClass, const AbstractMetaFunctionCPtr &func, QString *errorMessage); + static bool extractEnumDocumentation(const ClassDocumentation &classDocumentation, + AbstractMetaEnum &meta_enum); + }; #endif // QTDOCPARSER_H diff --git a/sources/shiboken6/ApiExtractor/tests/testabstractmetaclass.cpp b/sources/shiboken6/ApiExtractor/tests/testabstractmetaclass.cpp index a1940b76..4b5da0c3 100644 --- a/sources/shiboken6/ApiExtractor/tests/testabstractmetaclass.cpp +++ b/sources/shiboken6/ApiExtractor/tests/testabstractmetaclass.cpp @@ -113,10 +113,10 @@ public: QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); QCOMPARE(classes.size(), 4); - const auto a = AbstractMetaClass::findClass(classes, u"A"); - const auto b = AbstractMetaClass::findClass(classes, u"B"); - const auto c = AbstractMetaClass::findClass(classes, u"C"); - const auto f = AbstractMetaClass::findClass(classes, u"F"); + const auto a = AbstractMetaClass::findClass(classes, "A"); + const auto b = AbstractMetaClass::findClass(classes, "B"); + const auto c = AbstractMetaClass::findClass(classes, "C"); + const auto f = AbstractMetaClass::findClass(classes, "F"); QVERIFY(f); QCOMPARE(a->baseClass(), nullptr); @@ -159,11 +159,11 @@ public: const auto funcF = virtualFunctionsF.constFirst(); QCOMPARE(funcA->ownerClass(), a); - QVERIFY(funcC->attributes().testFlag(AbstractMetaFunction::VirtualCppMethod)); + QVERIFY(funcC->isVirtual()); QCOMPARE(funcB->ownerClass(), b); QCOMPARE(funcC->ownerClass(), c); - QVERIFY(funcC->attributes().testFlag(AbstractMetaFunction::OverriddenCppMethod)); - QVERIFY(funcF->attributes().testFlag(AbstractMetaFunction::FinalCppMethod)); + QVERIFY(funcC->cppAttributes().testFlag(FunctionAttribute::Override)); + QVERIFY(funcF->cppAttributes().testFlag(FunctionAttribute::Final)); QCOMPARE(funcA->declaringClass(), a); QCOMPARE(funcB->declaringClass(), a); @@ -196,10 +196,10 @@ class Derived : public Base {}; QScopedPointer builder(TestUtil::parse(cppCode, xmlCode)); QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto base = AbstractMetaClass::findClass(classes, u"Base"); + const auto base = AbstractMetaClass::findClass(classes, "Base"); QVERIFY(base); QVERIFY(base->isPolymorphic()); - const auto derived = AbstractMetaClass::findClass(classes, u"Derived"); + const auto derived = AbstractMetaClass::findClass(classes, "Derived"); QVERIFY(derived); QVERIFY(derived->isPolymorphic()); } @@ -221,7 +221,7 @@ void TestAbstractMetaClass::testDefaultValues() QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); QCOMPARE(classes.size(), 2); - const auto classA = AbstractMetaClass::findClass(classes, u"A"); + const auto classA = AbstractMetaClass::findClass(classes, "A"); const auto candidates = classA->queryFunctionsByName(u"method"_s); QCOMPARE(candidates.size(), 1); const auto &method = candidates.constFirst(); @@ -251,7 +251,7 @@ void TestAbstractMetaClass::testModifiedDefaultValues() QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); QCOMPARE(classes.size(), 2); - const auto classA = AbstractMetaClass::findClass(classes, u"A"); + const auto classA = AbstractMetaClass::findClass(classes, "A"); const auto methodMatches = classA->queryFunctionsByName(u"method"_s); QCOMPARE(methodMatches.size(), 1); const auto method = methodMatches.constFirst(); @@ -277,10 +277,10 @@ void TestAbstractMetaClass::testInnerClassOfAPolymorphicOne() QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); QCOMPARE(classes.size(), 2); - const auto classA = AbstractMetaClass::findClass(classes, u"A"); + const auto classA = AbstractMetaClass::findClass(classes, "A"); QVERIFY(classA); QVERIFY(classA->isPolymorphic()); - const auto classB = AbstractMetaClass::findClass(classes, u"A::B"); + const auto classB = AbstractMetaClass::findClass(classes, "A::B"); QVERIFY(classB); QVERIFY(!classB->isPolymorphic()); } @@ -305,11 +305,11 @@ void TestAbstractMetaClass::testForwardDeclaredInnerClass() QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); QCOMPARE(classes.size(), 2); - const auto classA = AbstractMetaClass::findClass(classes, u"A"); + const auto classA = AbstractMetaClass::findClass(classes, "A"); QVERIFY(classA); - const auto classB = AbstractMetaClass::findClass(classes, u"A::B"); + const auto classB = AbstractMetaClass::findClass(classes, "A::B"); QVERIFY(classB); - const auto fooF = classB->findFunction(u"foo"); + const auto fooF = classB->findFunction("foo"); QVERIFY(fooF); } @@ -337,7 +337,7 @@ void TestAbstractMetaClass::testSpecialFunctions() AbstractMetaClassList classes = builder->classes(); QCOMPARE(classes.size(), 2); - const auto classA = AbstractMetaClass::findClass(classes, u"A"); + const auto classA = AbstractMetaClass::findClass(classes, "A"); QVERIFY(classA); auto ctors = classA->queryFunctions(FunctionQueryOption::AnyConstructor); QCOMPARE(ctors.size(), 2); @@ -348,7 +348,7 @@ void TestAbstractMetaClass::testSpecialFunctions() QCOMPARE(assigmentOps.constFirst()->functionType(), AbstractMetaFunction::AssignmentOperatorFunction); - const auto classB = AbstractMetaClass::findClass(classes, u"B"); + const auto classB = AbstractMetaClass::findClass(classes, "B"); QVERIFY(classB); ctors = classB->queryFunctions(FunctionQueryOption::AnyConstructor); QCOMPARE(ctors.size(), 2); @@ -403,7 +403,7 @@ void TestAbstractMetaClass::testClassDefaultConstructors() AbstractMetaClassList classes = builder->classes(); QCOMPARE(classes.size(), 6); - const auto classA = AbstractMetaClass::findClass(classes, u"A"); + const auto classA = AbstractMetaClass::findClass(classes, "A"); QVERIFY(classA); QCOMPARE(classA->functions().size(), 2); @@ -417,28 +417,28 @@ void TestAbstractMetaClass::testClassDefaultConstructors() QCOMPARE(ctors[1]->arguments().size(), 1); QCOMPARE(ctors[1]->minimalSignature(), u"A(A)"); - const auto classB = AbstractMetaClass::findClass(classes, u"B"); + const auto classB = AbstractMetaClass::findClass(classes, "B"); QVERIFY(classB); QCOMPARE(classB->functions().size(), 2); QCOMPARE(classB->functions().constFirst()->minimalSignature(), u"B()"); - const auto classC = AbstractMetaClass::findClass(classes, u"C"); + const auto classC = AbstractMetaClass::findClass(classes, "C"); QVERIFY(classC); QCOMPARE(classC->functions().size(), 1); QCOMPARE(classC->functions().constFirst()->minimalSignature(), u"C(C)"); - const auto classD = AbstractMetaClass::findClass(classes, u"D"); + const auto classD = AbstractMetaClass::findClass(classes, "D"); QVERIFY(classD); QCOMPARE(classD->functions().size(), 1); QCOMPARE(classD->functions().constFirst()->minimalSignature(), u"D(D)"); QVERIFY(classD->functions().constFirst()->isPrivate()); - const auto classE = AbstractMetaClass::findClass(classes, u"E"); + const auto classE = AbstractMetaClass::findClass(classes, "E"); QVERIFY(classE); QVERIFY(classE->hasPrivateDestructor()); QCOMPARE(classE->functions().size(), 0); - const auto classF = AbstractMetaClass::findClass(classes, u"F"); + const auto classF = AbstractMetaClass::findClass(classes, "F"); QVERIFY(classF); ctors = classF->queryFunctions(FunctionQueryOption::AnyConstructor); @@ -471,7 +471,7 @@ void TestAbstractMetaClass::testClassInheritedDefaultConstructors() QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); QCOMPARE(classes.size(), 2); - const auto classA = AbstractMetaClass::findClass(classes, u"A"); + const auto classA = AbstractMetaClass::findClass(classes, "A"); QVERIFY(classA); auto ctors = classA->queryFunctions(FunctionQueryOption::AnyConstructor); @@ -485,7 +485,7 @@ void TestAbstractMetaClass::testClassInheritedDefaultConstructors() QCOMPARE(ctors[1]->minimalSignature(), u"A(A)"); QVERIFY(ctors[1]->isPrivate()); - const auto classB = AbstractMetaClass::findClass(classes, u"B"); + const auto classB = AbstractMetaClass::findClass(classes, "B"); QVERIFY(classB); ctors = classB->queryFunctions(FunctionQueryOption::Constructors); @@ -509,7 +509,7 @@ void TestAbstractMetaClass::testAbstractClassDefaultConstructors() QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); QCOMPARE(classes.size(), 1); - const auto classA = AbstractMetaClass::findClass(classes, u"A"); + const auto classA = AbstractMetaClass::findClass(classes, "A"); QVERIFY(classA); const auto ctors = classA->queryFunctions(FunctionQueryOption::Constructors); @@ -530,7 +530,7 @@ void TestAbstractMetaClass::testObjectTypesMustNotHaveCopyConstructors() QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); QCOMPARE(classes.size(), 1); - const auto classA = AbstractMetaClass::findClass(classes, u"A"); + const auto classA = AbstractMetaClass::findClass(classes, "A"); QVERIFY(classA); const auto ctors = classA->queryFunctions(FunctionQueryOption::Constructors); @@ -566,10 +566,10 @@ void TestAbstractMetaClass::testIsPolymorphic() QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); QCOMPARE(classes.size(), 2); - const auto b = AbstractMetaClass::findClass(classes, u"A"); + const auto b = AbstractMetaClass::findClass(classes, "A"); QVERIFY(!b->isPolymorphic()); - const auto a = AbstractMetaClass::findClass(classes, u"B"); + const auto a = AbstractMetaClass::findClass(classes, "B"); QVERIFY(!a->isPolymorphic()); } @@ -596,9 +596,9 @@ class Derived : public BaseAlias2 { QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); QCOMPARE(classes.size(), 2); - const auto base = AbstractMetaClass::findClass(classes, u"Base"); + const auto base = AbstractMetaClass::findClass(classes, "Base"); QVERIFY(base); - const auto derived = AbstractMetaClass::findClass(classes, u"Derived"); + const auto derived = AbstractMetaClass::findClass(classes, "Derived"); QVERIFY(derived); QCOMPARE(derived->baseClasses().value(0), base); } @@ -681,9 +681,9 @@ public: QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); QCOMPARE(classes.size(), 2); - const auto base = AbstractMetaClass::findClass(classes, u"Base"); + const auto base = AbstractMetaClass::findClass(classes, "Base"); QVERIFY(base); - const auto derived = AbstractMetaClass::findClass(classes, u"Derived"); + const auto derived = AbstractMetaClass::findClass(classes, "Derived"); QVERIFY(derived); const auto usingMembers = derived->usingMembers(); QCOMPARE(usingMembers.size(), 2); @@ -735,7 +735,7 @@ void TestAbstractMetaClass::testUsingTemplateMembers() QScopedPointer builder(TestUtil::parse(code.constData(), xmlCode)); QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto valueList = AbstractMetaClass::findClass(classes, u"ValueList"); + const auto valueList = AbstractMetaClass::findClass(classes, "ValueList"); QVERIFY(valueList); auto list = valueList->templateBaseClass(); QVERIFY(valueList->isUsingMember(list, u"append"_s, Access::Public)); @@ -765,7 +765,7 @@ public: QScopedPointer builder(TestUtil::parse(cppCode, xmlCode)); QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto tc = AbstractMetaClass::findClass(classes, u"TestClass"); + const auto tc = AbstractMetaClass::findClass(classes, "TestClass"); // Verify that the constructor and 2 functions are generated. const auto &functions = tc->functions(); QCOMPARE(functions.size(), 5); diff --git a/sources/shiboken6/ApiExtractor/tests/testabstractmetatype.cpp b/sources/shiboken6/ApiExtractor/tests/testabstractmetatype.cpp index 4a2821db..2c320c87 100644 --- a/sources/shiboken6/ApiExtractor/tests/testabstractmetatype.cpp +++ b/sources/shiboken6/ApiExtractor/tests/testabstractmetatype.cpp @@ -161,7 +161,7 @@ void TestAbstractMetaType::testTypedef() AbstractMetaClassList classes = builder->classes(); QCOMPARE(classes.size(), 1); - const auto c = AbstractMetaClass::findClass(classes, u"C"); + const auto c = AbstractMetaClass::findClass(classes, "C"); QVERIFY(c); QVERIFY(c->isTypeDef()); } @@ -211,7 +211,7 @@ void TestAbstractMetaType::testObjectTypeUsedAsValue() AbstractMetaClassList classes = builder->classes(); QCOMPARE(classes.size(), 1); - const auto classA = AbstractMetaClass::findClass(classes, u"A"); + const auto classA = AbstractMetaClass::findClass(classes, "A"); QVERIFY(classA); const auto overloads = classA->queryFunctionsByName(u"method"_s); QCOMPARE(overloads.size(), 1); diff --git a/sources/shiboken6/ApiExtractor/tests/testaddfunction.cpp b/sources/shiboken6/ApiExtractor/tests/testaddfunction.cpp index c3d6e4a8..a891e1e2 100644 --- a/sources/shiboken6/ApiExtractor/tests/testaddfunction.cpp +++ b/sources/shiboken6/ApiExtractor/tests/testaddfunction.cpp @@ -19,25 +19,28 @@ using namespace Qt::StringLiterals; +static constexpr auto voidT = "void"_L1; + void TestAddFunction::testParsingFuncNameAndConstness() { // generic test... - const char sig1[] = "func(type1, const type2, const type3* const)"; + static constexpr auto sig1 = "func(type1, const type2, const type3* const)"_L1; QString errorMessage; - auto f1 = AddedFunction::createAddedFunction(QLatin1StringView(sig1), u"void"_s, - &errorMessage); + auto f1 = AddedFunction::createAddedFunction(sig1, voidT, &errorMessage); QVERIFY2(f1, qPrintable(errorMessage)); QCOMPARE(f1->name(), u"func"); QCOMPARE(f1->arguments().size(), 3); TypeInfo retval = f1->returnType(); - QCOMPARE(retval.qualifiedName(), QStringList{u"void"_s}); + QCOMPARE(retval.qualifiedName(), QStringList{voidT}); QCOMPARE(retval.indirections(), 0); QCOMPARE(retval.isConstant(), false); QCOMPARE(retval.referenceType(), NoReference); // test with a ugly template as argument and other ugly stuff - const char sig2[] = " _fu__nc_ ( type1, const type2, const Abc * > * *@my_name@, const type3* const ) const "; - auto f2 = AddedFunction::createAddedFunction(QLatin1StringView(sig2), + static constexpr auto sig2 = + " _fu__nc_ ( type1, const type2, const Abc * >" + " * *@my_name@, const type3* const ) const "_L1; + auto f2 = AddedFunction::createAddedFunction(sig2, u"const Abc * > * *"_s, &errorMessage); QVERIFY2(f2, qPrintable(errorMessage)); @@ -66,17 +69,14 @@ void TestAddFunction::testParsingFuncNameAndConstness() QVERIFY(args.at(3).name.isEmpty()); // function with no args. - const char sig3[] = "func()"; - auto f3 = AddedFunction::createAddedFunction(QLatin1StringView(sig3), u"void"_s, - &errorMessage); + auto f3 = AddedFunction::createAddedFunction("func()"_L1, voidT, &errorMessage); QVERIFY2(f3, qPrintable(errorMessage)); QCOMPARE(f3->name(), u"func"); QCOMPARE(f3->arguments().size(), 0); // const call operator - const char sig4[] = "operator()(int)const"; - auto f4 = AddedFunction::createAddedFunction(QLatin1StringView(sig4), u"int"_s, - &errorMessage); + auto f4 = AddedFunction::createAddedFunction("operator()(int)const"_L1, + "int"_L1, &errorMessage); QVERIFY2(f4, qPrintable(errorMessage)); QCOMPARE(f4->name(), u"operator()"); QCOMPARE(f4->arguments().size(), 1); @@ -105,12 +105,12 @@ struct A { QVERIFY(builder); auto *typeDb = TypeDatabase::instance(); AbstractMetaClassList classes = builder->classes(); - const auto classA = AbstractMetaClass::findClass(classes, u"A"); + const auto classA = AbstractMetaClass::findClass(classes, "A"); QVERIFY(classA); // default ctor, default copy ctor, func a() and the added functions QCOMPARE(classA->functions().size(), 5); - auto addedFunc = classA->findFunction(u"b"); + auto addedFunc = classA->findFunction("b"); QVERIFY(addedFunc); QCOMPARE(addedFunc->access(), Access::Protected); QCOMPARE(addedFunc->functionType(), AbstractMetaFunction::NormalFunction); @@ -131,7 +131,7 @@ struct A { QCOMPARE(args.at(1).defaultValueExpression(), u"4.6"); QCOMPARE(args.at(2).type().typeEntry(), typeDb->findType(u"B"_s)); - auto addedCallOperator = classA->findFunction(u"operator()"); + auto addedCallOperator = classA->findFunction("operator()"); QVERIFY(addedCallOperator); } @@ -148,7 +148,7 @@ void TestAddFunction::testAddFunctionConstructor() QScopedPointer builder(TestUtil::parse(cppCode, xmlCode)); QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto classA = AbstractMetaClass::findClass(classes, u"A"); + const auto classA = AbstractMetaClass::findClass(classes, "A"); QVERIFY(classA); QCOMPARE(classA->functions().size(), 3); // default and added ctors const auto addedFunc = classA->functions().constLast(); @@ -171,7 +171,7 @@ void TestAddFunction::testAddFunctionTagDefaultValues() QScopedPointer builder(TestUtil::parse(cppCode, xmlCode)); QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto classA = AbstractMetaClass::findClass(classes, u"A"); + const auto classA = AbstractMetaClass::findClass(classes, "A"); QVERIFY(classA); // default ctor, default copy ctor and the added function QCOMPARE(classA->functions().size(), 3); @@ -197,7 +197,7 @@ void TestAddFunction::testAddFunctionCodeSnippets() QScopedPointer builder(TestUtil::parse(cppCode, xmlCode)); QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto classA = AbstractMetaClass::findClass(classes, u"A"); + const auto classA = AbstractMetaClass::findClass(classes, "A"); QVERIFY(classA); const auto addedFunc = classA->functions().constLast(); QVERIFY(addedFunc->hasInjectedCode()); @@ -205,10 +205,9 @@ void TestAddFunction::testAddFunctionCodeSnippets() void TestAddFunction::testAddFunctionWithoutParenteses() { - const char sig1[] = "func"; + static constexpr auto sig1 = "func"_L1; QString errorMessage; - auto f1 = AddedFunction::createAddedFunction(QLatin1StringView(sig1), u"void"_s, - &errorMessage); + auto f1 = AddedFunction::createAddedFunction(sig1, voidT, &errorMessage); QVERIFY2(f1, qPrintable(errorMessage)); QCOMPARE(f1->name(), u"func"); QCOMPARE(f1->arguments().size(), 0); @@ -227,9 +226,9 @@ void TestAddFunction::testAddFunctionWithoutParenteses() QScopedPointer builder(TestUtil::parse(cppCode, xmlCode)); QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto classA = AbstractMetaClass::findClass(classes, u"A"); + const auto classA = AbstractMetaClass::findClass(classes, "A"); QVERIFY(classA); - const auto addedFunc = classA->findFunction(u"func"); + const auto addedFunc = classA->findFunction(sig1); QVERIFY(addedFunc); QVERIFY(addedFunc->hasInjectedCode()); const auto snips = addedFunc->injectedCodeSnips(TypeSystem::CodeSnipPositionAny, @@ -239,10 +238,9 @@ void TestAddFunction::testAddFunctionWithoutParenteses() void TestAddFunction::testAddFunctionWithDefaultArgs() { - const char sig1[] = "func"; + static constexpr auto sig1 = "func"_L1; QString errorMessage; - auto f1 = AddedFunction::createAddedFunction(QLatin1StringView(sig1), u"void"_s, - &errorMessage); + auto f1 = AddedFunction::createAddedFunction(sig1, voidT, &errorMessage); QVERIFY2(f1, qPrintable(errorMessage)); QCOMPARE(f1->name(), u"func"); QCOMPARE(f1->arguments().size(), 0); @@ -264,9 +262,9 @@ void TestAddFunction::testAddFunctionWithDefaultArgs() QScopedPointer builder(TestUtil::parse(cppCode, xmlCode)); QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto classA = AbstractMetaClass::findClass(classes, u"A"); + const auto classA = AbstractMetaClass::findClass(classes, "A"); QVERIFY(classA); - const auto addedFunc = classA->findFunction(u"func"); + const auto addedFunc = classA->findFunction(sig1); QVERIFY(addedFunc); const AbstractMetaArgument &arg = addedFunc->arguments().at(1); QCOMPARE(arg.defaultValueExpression(), u"2"); @@ -287,7 +285,7 @@ void TestAddFunction::testAddFunctionAtModuleLevel() QScopedPointer builder(TestUtil::parse(cppCode, xmlCode)); QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto classA = AbstractMetaClass::findClass(classes, u"A"); + const auto classA = AbstractMetaClass::findClass(classes, "A"); QVERIFY(classA); auto *typeDb = TypeDatabase::instance(); @@ -306,9 +304,8 @@ void TestAddFunction::testAddFunctionAtModuleLevel() void TestAddFunction::testAddFunctionWithVarargs() { - const char sig1[] = "func(int,char,...)"; QString errorMessage; - auto f1 = AddedFunction::createAddedFunction(QLatin1StringView(sig1), u"void"_s, + auto f1 = AddedFunction::createAddedFunction("func(int,char,...)"_L1, voidT, &errorMessage); QVERIFY2(f1, qPrintable(errorMessage)); QCOMPARE(f1->name(), u"func"); @@ -328,9 +325,9 @@ void TestAddFunction::testAddFunctionWithVarargs() QScopedPointer builder(TestUtil::parse(cppCode, xmlCode)); QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto classA = AbstractMetaClass::findClass(classes, u"A"); + const auto classA = AbstractMetaClass::findClass(classes, "A"); QVERIFY(classA); - const auto addedFunc = classA->findFunction(u"func"); + const auto addedFunc = classA->findFunction("func"); QVERIFY(addedFunc); const AbstractMetaArgument &arg = addedFunc->arguments().constLast(); QVERIFY(arg.type().isVarargs()); @@ -352,9 +349,9 @@ void TestAddFunction::testAddStaticFunction() QScopedPointer builder(TestUtil::parse(cppCode, xmlCode)); QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto classA = AbstractMetaClass::findClass(classes, u"A"); + const auto classA = AbstractMetaClass::findClass(classes, "A"); QVERIFY(classA); - const auto addedFunc = classA->findFunction(u"func"); + const auto addedFunc = classA->findFunction("func"); QVERIFY(addedFunc); QVERIFY(addedFunc->isStatic()); } @@ -378,10 +375,10 @@ void TestAddFunction::testAddGlobalFunction() QVERIFY(builder); const auto globalFuncs = builder->globalFunctions(); QCOMPARE(globalFuncs.size(), 2); - const auto classB = AbstractMetaClass::findClass(builder->classes(), u"B"); + const auto classB = AbstractMetaClass::findClass(builder->classes(), "B"); QVERIFY(classB); - QVERIFY(!classB->findFunction(u"globalFunc")); - QVERIFY(!classB->findFunction(u"globalFunc2")); + QVERIFY(!classB->findFunction("globalFunc")); + QVERIFY(!classB->findFunction("globalFunc2")); QVERIFY(!globalFuncs[0]->injectedCodeSnips().isEmpty()); QVERIFY(!globalFuncs[1]->injectedCodeSnips().isEmpty()); } @@ -426,8 +423,8 @@ void TestAddFunction::testModifyAddedFunction() QScopedPointer builder(TestUtil::parse(cppCode, xmlCode)); QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto foo = AbstractMetaClass::findClass(classes, u"Foo"); - const auto method = foo->findFunction(u"method"); + const auto foo = AbstractMetaClass::findClass(classes, "Foo"); + const auto method = foo->findFunction("method"); QVERIFY(method); QCOMPARE(method->arguments().size(), 2); const AbstractMetaArgument &arg = method->arguments().at(1); @@ -453,14 +450,14 @@ void TestAddFunction::testAddFunctionOnTypedef() QScopedPointer builder(TestUtil::parse(cppCode, xmlCode)); QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto foo = AbstractMetaClass::findClass(classes, u"FooInt"); + const auto foo = AbstractMetaClass::findClass(classes, "FooInt"); QVERIFY(foo); QVERIFY(foo->hasNonPrivateConstructor()); const auto &lst = foo->queryFunctions(FunctionQueryOption::AnyConstructor); for (const auto &f : lst) QVERIFY(f->signature().startsWith(f->name())); QCOMPARE(lst.size(), 2); - const auto method = foo->findFunction(u"method"); + const auto method = foo->findFunction("method"); QVERIFY(method); } diff --git a/sources/shiboken6/ApiExtractor/tests/testarrayargument.cpp b/sources/shiboken6/ApiExtractor/tests/testarrayargument.cpp index f4b22b27..6e1820be 100644 --- a/sources/shiboken6/ApiExtractor/tests/testarrayargument.cpp +++ b/sources/shiboken6/ApiExtractor/tests/testarrayargument.cpp @@ -34,7 +34,7 @@ void TestArrayArgument::testArrayArgumentWithSizeDefinedByInteger() QScopedPointer builder(TestUtil::parse(cppCode, xmlCode, false)); QVERIFY(!builder.isNull()); - const auto classA = AbstractMetaClass::findClass(builder->classes(), u"A"); + const auto classA = AbstractMetaClass::findClass(builder->classes(), "A"); QVERIFY(classA); const AbstractMetaArgument &arg = classA->functions().constLast()->arguments().constFirst(); @@ -72,7 +72,7 @@ void TestArrayArgument::testArraySignature() QScopedPointer builder(TestUtil::parse(cppCode, xmlCode, false)); QVERIFY(!builder.isNull()); - const auto classA = AbstractMetaClass::findClass(builder->classes(), u"A"); + const auto classA = AbstractMetaClass::findClass(builder->classes(), "A"); QCOMPARE(functionMinimalSignature(classA, u"mi1"_s), u"mi1(int[5])"); QCOMPARE(functionMinimalSignature(classA, u"mi1c"_s), @@ -108,7 +108,7 @@ void TestArrayArgument::testArrayArgumentWithSizeDefinedByEnumValue() QScopedPointer builder(TestUtil::parse(cppCode, xmlCode, false)); QVERIFY(!builder.isNull()); - AbstractMetaClassPtr classA = AbstractMetaClass::findClass(builder->classes(), u"A"); + AbstractMetaClassPtr classA = AbstractMetaClass::findClass(builder->classes(), "A"); QVERIFY(classA); auto someEnum = classA->findEnum(u"SomeEnum"_s); @@ -139,7 +139,7 @@ void TestArrayArgument::testArrayArgumentWithSizeDefinedByEnumValueFromGlobalEnu QScopedPointer builder(TestUtil::parse(cppCode, xmlCode, false)); QVERIFY(builder); - const auto classA = AbstractMetaClass::findClass(builder->classes(), u"A"); + const auto classA = AbstractMetaClass::findClass(builder->classes(), "A"); QVERIFY(classA); AbstractMetaEnum someEnum = builder->globalEnums().constFirst(); diff --git a/sources/shiboken6/ApiExtractor/tests/testcodeinjection.cpp b/sources/shiboken6/ApiExtractor/tests/testcodeinjection.cpp index 58aaaf9b..4829e6c3 100644 --- a/sources/shiboken6/ApiExtractor/tests/testcodeinjection.cpp +++ b/sources/shiboken6/ApiExtractor/tests/testcodeinjection.cpp @@ -61,7 +61,7 @@ void TestCodeInjections::testReadFile() QScopedPointer builder(TestUtil::parse(cppCode, xmlCode.toLocal8Bit().constData())); QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto classA = AbstractMetaClass::findClass(classes, u"A"); + const auto classA = AbstractMetaClass::findClass(classes, "A"); QCOMPARE(classA->typeEntry()->codeSnips().size(), 1); QString code = classA->typeEntry()->codeSnips().constFirst().code(); QVERIFY(code.indexOf(expected) != -1); @@ -87,7 +87,7 @@ void TestCodeInjections::testInjectWithValidApiVersion() true, u"1.0"_s)); QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto classA = AbstractMetaClass::findClass(classes, u"A"); + const auto classA = AbstractMetaClass::findClass(classes, "A"); QCOMPARE(classA->typeEntry()->codeSnips().size(), 1); } @@ -108,7 +108,7 @@ void TestCodeInjections::testInjectWithInvalidApiVersion() QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto classA = AbstractMetaClass::findClass(classes, u"A"); + const auto classA = AbstractMetaClass::findClass(classes, "A"); QCOMPARE(classA->typeEntry()->codeSnips().size(), 0); } @@ -125,7 +125,7 @@ void TestCodeInjections::testTextStream() str << "\n2nd table\n|" << AlignedField("bla", 3, QTextStream::AlignLeft) << '|' << AlignedField(QString{}, 0, QTextStream::AlignLeft) << "|\n"; -static const char expected[] = R"(void foo(int a, int b) { +constexpr auto expected = R"(void foo(int a, int b) { if (a == b) return a; #if Q_OS_WIN @@ -141,9 +141,9 @@ static const char expected[] = R"(void foo(int a, int b) { 2nd table |bla|| -)"; +)"_L1; - QCOMPARE(str.toString(), QLatin1String(expected)); + QCOMPARE(str.toString(), expected); } void TestCodeInjections::testTextStreamRst() diff --git a/sources/shiboken6/ApiExtractor/tests/testcontainer.cpp b/sources/shiboken6/ApiExtractor/tests/testcontainer.cpp index 5a9b7d18..0bb72b3c 100644 --- a/sources/shiboken6/ApiExtractor/tests/testcontainer.cpp +++ b/sources/shiboken6/ApiExtractor/tests/testcontainer.cpp @@ -32,7 +32,7 @@ void TestContainer::testContainerType() AbstractMetaClassList classes = builder->classes(); QCOMPARE(classes.size(), 2); //search for class A - const auto classA = AbstractMetaClass::findClass(classes, u"A"); + const auto classA = AbstractMetaClass::findClass(classes, "A"); QVERIFY(classA); auto baseContainer = classA->typeEntry()->baseContainerType(); QVERIFY(baseContainer); @@ -65,7 +65,7 @@ void TestContainer::testListOfValueType() AbstractMetaClassList classes = builder->classes(); QCOMPARE(classes.size(), 3); - const auto classA = AbstractMetaClass::findClass(classes, u"A"); + const auto classA = AbstractMetaClass::findClass(classes, "A"); QVERIFY(classA); QCOMPARE(classA->templateBaseClassInstantiations().size(), 1); const AbstractMetaType templateInstanceType = diff --git a/sources/shiboken6/ApiExtractor/tests/testconversionoperator.cpp b/sources/shiboken6/ApiExtractor/tests/testconversionoperator.cpp index 09250362..8f2b277a 100644 --- a/sources/shiboken6/ApiExtractor/tests/testconversionoperator.cpp +++ b/sources/shiboken6/ApiExtractor/tests/testconversionoperator.cpp @@ -35,9 +35,9 @@ void TestConversionOperator::testConversionOperator() QScopedPointer builder(TestUtil::parse(cppCode, xmlCode)); QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto classA = AbstractMetaClass::findClass(classes, u"A"); - const auto classB = AbstractMetaClass::findClass(classes, u"B"); - const auto classC = AbstractMetaClass::findClass(classes, u"C"); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + const auto classB = AbstractMetaClass::findClass(classes, "B"); + const auto classC = AbstractMetaClass::findClass(classes, "C"); QVERIFY(classA); QVERIFY(classB); QVERIFY(classC); @@ -73,7 +73,7 @@ void TestConversionOperator::testConversionOperatorOfDiscardedClass() QScopedPointer builder(TestUtil::parse(cppCode, xmlCode)); QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto classA = AbstractMetaClass::findClass(classes, u"A"); + const auto classA = AbstractMetaClass::findClass(classes, "A"); QVERIFY(classA); QCOMPARE(classA->externalConversionOperators().size(), 0); } @@ -97,8 +97,8 @@ void TestConversionOperator::testRemovedConversionOperator() QScopedPointer builder(TestUtil::parse(cppCode, xmlCode)); QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto classA = AbstractMetaClass::findClass(classes, u"A"); - const auto classB = AbstractMetaClass::findClass(classes, u"B"); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + const auto classB = AbstractMetaClass::findClass(classes, "B"); QVERIFY(classA); QVERIFY(classB); QCOMPARE(classA->functions().size(), 2); @@ -123,8 +123,8 @@ void TestConversionOperator::testConversionOperatorReturningReference() QScopedPointer builder(TestUtil::parse(cppCode, xmlCode)); QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto classA = AbstractMetaClass::findClass(classes, u"A"); - const auto classB = AbstractMetaClass::findClass(classes, u"B"); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + const auto classB = AbstractMetaClass::findClass(classes, "B"); QVERIFY(classA); QVERIFY(classB); QCOMPARE(classA->functions().size(), 2); @@ -157,8 +157,8 @@ void TestConversionOperator::testConversionOperatorReturningConstReference() QScopedPointer builder(TestUtil::parse(cppCode, xmlCode)); QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto classA = AbstractMetaClass::findClass(classes, u"A"); - const auto classB = AbstractMetaClass::findClass(classes, u"B"); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + const auto classB = AbstractMetaClass::findClass(classes, "B"); QVERIFY(classA); QVERIFY(classB); QCOMPARE(classA->functions().size(), 2); diff --git a/sources/shiboken6/ApiExtractor/tests/testconversionruletag.cpp b/sources/shiboken6/ApiExtractor/tests/testconversionruletag.cpp index 2d754427..b5efd92a 100644 --- a/sources/shiboken6/ApiExtractor/tests/testconversionruletag.cpp +++ b/sources/shiboken6/ApiExtractor/tests/testconversionruletag.cpp @@ -21,10 +21,10 @@ void TestConversionRuleTag::testConversionRuleTagWithFile() { // FIXME PYSIDE7 remove // temp file used later - const char conversionData[] = "Hi! I'm a conversion rule."; + constexpr auto conversionData = "Hi! I'm a conversion rule."_L1; QTemporaryFile file; - file.open(); - QCOMPARE(file.write(conversionData), qint64(sizeof(conversionData)-1)); + QVERIFY(file.open()); + QCOMPARE(file.write(conversionData.constData()), conversionData.size()); file.close(); const char cppCode[] = "struct A {};\n"; @@ -37,13 +37,13 @@ void TestConversionRuleTag::testConversionRuleTagWithFile() QScopedPointer builder(TestUtil::parse(cppCode, xmlCode.toLocal8Bit().data())); QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto classA = AbstractMetaClass::findClass(classes, u"A"); + const auto classA = AbstractMetaClass::findClass(classes, "A"); QVERIFY(classA); const auto typeEntry = classA->typeEntry(); QVERIFY(typeEntry->isValue()); auto vte = std::static_pointer_cast(typeEntry); QVERIFY(vte->hasTargetConversionRule()); - QCOMPARE(vte->targetConversionRule(), QLatin1String(conversionData)); + QCOMPARE(vte->targetConversionRule(), conversionData); } void TestConversionRuleTag::testConversionRuleTagReplace() @@ -154,7 +154,7 @@ if (!TargetDateTimeAPI) TargetDateTime_IMPORT;\n\ QScopedPointer builder(TestUtil::parse(cppCode, xmlCode)); QVERIFY(builder); - const auto classA = AbstractMetaClass::findClass(builder->classes(), u"Date"); + const auto classA = AbstractMetaClass::findClass(builder->classes(), "Date"); QVERIFY(classA); QVERIFY(classA->typeEntry()->isValue()); diff --git a/sources/shiboken6/ApiExtractor/tests/testctorinformation.cpp b/sources/shiboken6/ApiExtractor/tests/testctorinformation.cpp index e225f91a..c3a3ebef 100644 --- a/sources/shiboken6/ApiExtractor/tests/testctorinformation.cpp +++ b/sources/shiboken6/ApiExtractor/tests/testctorinformation.cpp @@ -22,13 +22,13 @@ void TestCtorInformation::testCtorIsPrivate() QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); QCOMPARE(classes.size(), 3); - auto klass = AbstractMetaClass::findClass(classes, u"Control"); + auto klass = AbstractMetaClass::findClass(classes, "Control"); QVERIFY(klass); QVERIFY(klass->hasNonPrivateConstructor()); - klass = AbstractMetaClass::findClass(classes, u"Subject"); + klass = AbstractMetaClass::findClass(classes, "Subject"); QVERIFY(klass); QVERIFY(!klass->hasNonPrivateConstructor()); - klass = AbstractMetaClass::findClass(classes, u"CtorLess"); + klass = AbstractMetaClass::findClass(classes, "CtorLess"); QVERIFY(klass); QVERIFY(klass->hasNonPrivateConstructor()); } @@ -48,9 +48,9 @@ void TestCtorInformation::testHasNonPrivateCtor() QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); QCOMPARE(classes.size(), 2); - const auto base = AbstractMetaClass::findClass(classes, u"Base"); + const auto base = AbstractMetaClass::findClass(classes, "Base"); QCOMPARE(base->hasNonPrivateConstructor(), true); - const auto derived = AbstractMetaClass::findClass(classes, u"Derived"); + const auto derived = AbstractMetaClass::findClass(classes, "Derived"); QCOMPARE(derived->hasNonPrivateConstructor(), true); } diff --git a/sources/shiboken6/ApiExtractor/tests/testdroptypeentries.cpp b/sources/shiboken6/ApiExtractor/tests/testdroptypeentries.cpp index 0f65b1fc..16f50e69 100644 --- a/sources/shiboken6/ApiExtractor/tests/testdroptypeentries.cpp +++ b/sources/shiboken6/ApiExtractor/tests/testdroptypeentries.cpp @@ -59,13 +59,13 @@ void TestDropTypeEntries::testDropEntries() QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - QVERIFY(AbstractMetaClass::findClass(classes, u"ValueA")); - QVERIFY(!AbstractMetaClass::findClass(classes, u"ValueB")); - QVERIFY(AbstractMetaClass::findClass(classes, u"ObjectA")); - QVERIFY(!AbstractMetaClass::findClass(classes, u"ObjectB")); - QVERIFY(AbstractMetaClass::findClass(classes, u"NamespaceA")); - QVERIFY(!AbstractMetaClass::findClass(classes, u"NamespaceA::InnerClassA")); - QVERIFY(!AbstractMetaClass::findClass(classes, u"NamespaceB")); + QVERIFY(AbstractMetaClass::findClass(classes, "ValueA")); + QVERIFY(!AbstractMetaClass::findClass(classes, "ValueB")); + QVERIFY(AbstractMetaClass::findClass(classes, "ObjectA")); + QVERIFY(!AbstractMetaClass::findClass(classes, "ObjectB")); + QVERIFY(AbstractMetaClass::findClass(classes, "NamespaceA")); + QVERIFY(!AbstractMetaClass::findClass(classes, "NamespaceA::InnerClassA")); + QVERIFY(!AbstractMetaClass::findClass(classes, "NamespaceB")); AbstractMetaEnumList globalEnums = builder->globalEnums(); QCOMPARE(globalEnums.size(), 1); @@ -82,13 +82,13 @@ void TestDropTypeEntries::testDontDropEntries() QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - QVERIFY(AbstractMetaClass::findClass(classes, u"ValueA")); - QVERIFY(AbstractMetaClass::findClass(classes, u"ValueB")); - QVERIFY(AbstractMetaClass::findClass(classes, u"ObjectA")); - QVERIFY(AbstractMetaClass::findClass(classes, u"ObjectB")); - QVERIFY(AbstractMetaClass::findClass(classes, u"NamespaceA")); - QVERIFY(AbstractMetaClass::findClass(classes, u"NamespaceA::InnerClassA")); - QVERIFY(AbstractMetaClass::findClass(classes, u"NamespaceB")); + QVERIFY(AbstractMetaClass::findClass(classes, "ValueA")); + QVERIFY(AbstractMetaClass::findClass(classes, "ValueB")); + QVERIFY(AbstractMetaClass::findClass(classes, "ObjectA")); + QVERIFY(AbstractMetaClass::findClass(classes, "ObjectB")); + QVERIFY(AbstractMetaClass::findClass(classes, "NamespaceA")); + QVERIFY(AbstractMetaClass::findClass(classes, "NamespaceA::InnerClassA")); + QVERIFY(AbstractMetaClass::findClass(classes, "NamespaceB")); QCOMPARE(builder->globalEnums().size(), 2); @@ -116,7 +116,7 @@ void TestDropTypeEntries::testDropEntryWithChildTags() QScopedPointer builder(TestUtil::parse(cppCode2, xmlCode2, false, QString(), droppedEntries)); QVERIFY(builder); - QVERIFY(!AbstractMetaClass::findClass(builder->classes(), u"ValueA")); + QVERIFY(!AbstractMetaClass::findClass(builder->classes(), "ValueA")); } @@ -124,12 +124,12 @@ void TestDropTypeEntries::testDontDropEntryWithChildTags() { QScopedPointer builder(TestUtil::parse(cppCode2, xmlCode2, false)); QVERIFY(builder); - QVERIFY(AbstractMetaClass::findClass(builder->classes(), u"ValueA")); + QVERIFY(AbstractMetaClass::findClass(builder->classes(), "ValueA")); } void TestDropTypeEntries::testConditionalParsing_data() { - const QString xml = QStringLiteral(R"( + const QString xml = R"( text @@ -145,17 +145,17 @@ void TestDropTypeEntries::testConditionalParsing_data() text -)"); - - const QString root = QStringLiteral("root"); - const QString tag1 = QStringLiteral("tag1"); - const QString tag2 = QStringLiteral("tag2"); - const QString tag3 = QStringLiteral("tag3"); - const QString tag4 = QStringLiteral("tag4"); - const QString tag5 = QStringLiteral("tag5"); - const QString tag6 = QStringLiteral("tag6"); - const QString keyword1 = QStringLiteral("keyword1"); - const QString keyword2 = QStringLiteral("keyword2"); +)"_L1; + + constexpr auto root = "root"_L1; + constexpr auto tag1 = "tag1"_L1; + constexpr auto tag2 = "tag2"_L1; + constexpr auto tag3 = "tag3"_L1; + constexpr auto tag4 = "tag4"_L1; + constexpr auto tag5 = "tag5"_L1; + constexpr auto tag6 = "tag6"_L1; + constexpr auto keyword1 = "keyword1"_L1; + constexpr auto keyword2 = "keyword2"_L1; QTest::addColumn("xml"); QTest::addColumn("keywords"); @@ -204,11 +204,11 @@ void TestDropTypeEntries::testConditionalParsing() void TestDropTypeEntries::testEntityParsing() { - const QString xml = QStringLiteral(R"( + const QString xml = R"( bla &testentity; -)"); +)"_L1; QString actual; ConditionalStreamReader reader(xml); diff --git a/sources/shiboken6/ApiExtractor/tests/testdtorinformation.cpp b/sources/shiboken6/ApiExtractor/tests/testdtorinformation.cpp index de4cfd9f..2152d39d 100644 --- a/sources/shiboken6/ApiExtractor/tests/testdtorinformation.cpp +++ b/sources/shiboken6/ApiExtractor/tests/testdtorinformation.cpp @@ -28,10 +28,10 @@ private: QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); QCOMPARE(classes.size(), 2); - auto klass = AbstractMetaClass::findClass(classes, u"Control"); + auto klass = AbstractMetaClass::findClass(classes, "Control"); QVERIFY(klass); QVERIFY(!klass->hasPrivateDestructor()); - klass = AbstractMetaClass::findClass(classes, u"Subject"); + klass = AbstractMetaClass::findClass(classes, "Subject"); QVERIFY(klass); QVERIFY(klass->hasPrivateDestructor()); } @@ -56,10 +56,10 @@ protected: QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); QCOMPARE(classes.size(), 2); - auto klass = AbstractMetaClass::findClass(classes, u"Control"); + auto klass = AbstractMetaClass::findClass(classes, "Control"); QVERIFY(klass); QVERIFY(!klass->hasProtectedDestructor()); - klass = AbstractMetaClass::findClass(classes, u"Subject"); + klass = AbstractMetaClass::findClass(classes, "Subject"); QVERIFY(klass); QVERIFY(klass->hasProtectedDestructor()); } @@ -84,10 +84,10 @@ protected: QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); QCOMPARE(classes.size(), 2); - auto klass = AbstractMetaClass::findClass(classes, u"Control"); + auto klass = AbstractMetaClass::findClass(classes, "Control"); QVERIFY(klass); QVERIFY(!klass->hasVirtualDestructor()); - klass = AbstractMetaClass::findClass(classes, u"Subject"); + klass = AbstractMetaClass::findClass(classes, "Subject"); QVERIFY(klass); QVERIFY(klass->hasVirtualDestructor()); } @@ -110,17 +110,17 @@ class Subject : public SubjectBase {}; AbstractMetaClassList classes = builder->classes(); QCOMPARE(classes.size(), 4); - auto klass = AbstractMetaClass::findClass(classes, u"ControlBase"); + auto klass = AbstractMetaClass::findClass(classes, "ControlBase"); QVERIFY(klass); QVERIFY(!klass->hasVirtualDestructor()); - klass = AbstractMetaClass::findClass(classes, u"Control"); + klass = AbstractMetaClass::findClass(classes, "Control"); QVERIFY(klass); QVERIFY(!klass->hasVirtualDestructor()); - klass = AbstractMetaClass::findClass(classes, u"SubjectBase"); + klass = AbstractMetaClass::findClass(classes, "SubjectBase"); QVERIFY(klass); QVERIFY(klass->hasVirtualDestructor()); - klass = AbstractMetaClass::findClass(classes, u"Subject"); + klass = AbstractMetaClass::findClass(classes, "Subject"); QVERIFY(klass); QVERIFY(klass->hasVirtualDestructor()); } @@ -145,10 +145,10 @@ protected: QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); QCOMPARE(classes.size(), 2); - auto klass = AbstractMetaClass::findClass(classes, u"Control"); + auto klass = AbstractMetaClass::findClass(classes, "Control"); QVERIFY(klass); QVERIFY(klass->isPolymorphic()); - klass = AbstractMetaClass::findClass(classes, u"Subject"); + klass = AbstractMetaClass::findClass(classes, "Subject"); QVERIFY(klass); QVERIFY(klass->isPolymorphic()); } diff --git a/sources/shiboken6/ApiExtractor/tests/testenum.cpp b/sources/shiboken6/ApiExtractor/tests/testenum.cpp index 8a1f6c88..c7c2b8b3 100644 --- a/sources/shiboken6/ApiExtractor/tests/testenum.cpp +++ b/sources/shiboken6/ApiExtractor/tests/testenum.cpp @@ -54,7 +54,7 @@ void TestEnum::testEnumCppSignature() u"A::ClassEnum"); // enum as parameter of a method - const auto classA = AbstractMetaClass::findClass(classes, u"A"); + const auto classA = AbstractMetaClass::findClass(classes, "A"); QCOMPARE(classA->enums().size(), 1); const auto funcs = classA->queryFunctionsByName(u"method"_s); QVERIFY(!funcs.isEmpty()); @@ -286,7 +286,7 @@ void TestEnum::testEnumValueFromExpression() QScopedPointer builder(TestUtil::parse(cppCode, xmlCode, false)); QVERIFY(builder); - AbstractMetaClassPtr classA = AbstractMetaClass::findClass(builder->classes(), u"A"); + AbstractMetaClassPtr classA = AbstractMetaClass::findClass(builder->classes(), "A"); QVERIFY(classA); auto enumA = classA->findEnum(u"EnumA"_s); @@ -364,7 +364,7 @@ void TestEnum::testPrivateEnum() QScopedPointer builder(TestUtil::parse(cppCode, xmlCode, false)); QVERIFY(builder); - const auto classA = AbstractMetaClass::findClass(builder->classes(), u"A"); + const auto classA = AbstractMetaClass::findClass(builder->classes(), "A"); QVERIFY(classA); QCOMPARE(classA->enums().size(), 2); diff --git a/sources/shiboken6/ApiExtractor/tests/testextrainclude.cpp b/sources/shiboken6/ApiExtractor/tests/testextrainclude.cpp index 63d02bb7..fcc409a4 100644 --- a/sources/shiboken6/ApiExtractor/tests/testextrainclude.cpp +++ b/sources/shiboken6/ApiExtractor/tests/testextrainclude.cpp @@ -23,7 +23,7 @@ void TestExtraInclude::testClassExtraInclude() QScopedPointer builder(TestUtil::parse(cppCode, xmlCode, false)); QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto classA = AbstractMetaClass::findClass(classes, u"A"); + const auto classA = AbstractMetaClass::findClass(classes, "A"); QVERIFY(classA); QList includes = classA->typeEntry()->extraIncludes(); @@ -46,7 +46,7 @@ void TestExtraInclude::testGlobalExtraIncludes() QScopedPointer builder(TestUtil::parse(cppCode, xmlCode, false)); QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - QVERIFY(AbstractMetaClass::findClass(classes, u"A")); + QVERIFY(AbstractMetaClass::findClass(classes, "A")); auto *td = TypeDatabase::instance(); TypeSystemTypeEntryCPtr module = td->defaultTypeSystemType(); diff --git a/sources/shiboken6/ApiExtractor/tests/testimplicitconversions.cpp b/sources/shiboken6/ApiExtractor/tests/testimplicitconversions.cpp index 56b80aae..899d00ad 100644 --- a/sources/shiboken6/ApiExtractor/tests/testimplicitconversions.cpp +++ b/sources/shiboken6/ApiExtractor/tests/testimplicitconversions.cpp @@ -33,8 +33,8 @@ void TestImplicitConversions::testWithPrivateCtors() AbstractMetaClassList classes = builder->classes(); QCOMPARE(classes.size(), 3); - const auto classA = AbstractMetaClass::findClass(classes, u"A"); - const auto classC = AbstractMetaClass::findClass(classes, u"C"); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + const auto classC = AbstractMetaClass::findClass(classes, "C"); const auto implicitConvs = classA->implicitConversions(); QCOMPARE(implicitConvs.size(), 1); QCOMPARE(implicitConvs.constFirst()->arguments().constFirst().type().typeEntry(), @@ -62,8 +62,8 @@ void TestImplicitConversions::testWithModifiedVisibility() QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); QCOMPARE(classes.size(), 2); - const auto classA = AbstractMetaClass::findClass(classes, u"A"); - const auto classB = AbstractMetaClass::findClass(classes, u"B"); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + const auto classB = AbstractMetaClass::findClass(classes, "B"); const auto implicitConvs = classA->implicitConversions(); QCOMPARE(implicitConvs.size(), 1); QCOMPARE(implicitConvs.constFirst()->arguments().constFirst().type().typeEntry(), @@ -97,12 +97,12 @@ void TestImplicitConversions::testWithAddedCtor() AbstractMetaClassList classes = builder->classes(); QCOMPARE(classes.size(), 3); - const auto classA = AbstractMetaClass::findClass(classes, u"A"); + const auto classA = AbstractMetaClass::findClass(classes, "A"); auto implicitConvs = classA->implicitConversions(); QCOMPARE(implicitConvs.size(), 2); // Added constructors with custom types should never result in implicit converters. - const auto classB = AbstractMetaClass::findClass(classes, u"B"); + const auto classB = AbstractMetaClass::findClass(classes, "B"); implicitConvs = classB->implicitConversions(); QCOMPARE(implicitConvs.size(), 0); } @@ -123,8 +123,8 @@ void TestImplicitConversions::testWithExternalConversionOperator() QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); QCOMPARE(classes.size(), 2); - const auto classA = AbstractMetaClass::findClass(classes, u"A"); - const auto classB = AbstractMetaClass::findClass(classes, u"B"); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + const auto classB = AbstractMetaClass::findClass(classes, "B"); const auto implicitConvs = classA->implicitConversions(); QCOMPARE(implicitConvs.size(), 1); const auto &externalConvOps = classA->externalConversionOperators(); diff --git a/sources/shiboken6/ApiExtractor/tests/testinserttemplate.cpp b/sources/shiboken6/ApiExtractor/tests/testinserttemplate.cpp index 63992572..23cf0f9e 100644 --- a/sources/shiboken6/ApiExtractor/tests/testinserttemplate.cpp +++ b/sources/shiboken6/ApiExtractor/tests/testinserttemplate.cpp @@ -28,7 +28,7 @@ void TestInsertTemplate::testInsertTemplateOnClassInjectCode() QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); QCOMPARE(classes.size(), 1); - const auto classA = AbstractMetaClass::findClass(classes, u"A"); + const auto classA = AbstractMetaClass::findClass(classes, "A"); QVERIFY(classA); QCOMPARE(classA->typeEntry()->codeSnips().size(), 1); QString code = classA->typeEntry()->codeSnips().constFirst().code(); diff --git a/sources/shiboken6/ApiExtractor/tests/testmodifydocumentation.cpp b/sources/shiboken6/ApiExtractor/tests/testmodifydocumentation.cpp index ad9a071d..9cf2e0cc 100644 --- a/sources/shiboken6/ApiExtractor/tests/testmodifydocumentation.cpp +++ b/sources/shiboken6/ApiExtractor/tests/testmodifydocumentation.cpp @@ -34,7 +34,7 @@ R"( )"; QScopedPointer builder(TestUtil::parse(cppCode, xmlCode)); QVERIFY(builder); - const auto classA = AbstractMetaClass::findClass(builder->classes(), u"A"); + const auto classA = AbstractMetaClass::findClass(builder->classes(), "A"); QVERIFY(classA); DocModificationList docMods = classA->typeEntry()->docModifications(); QCOMPARE(docMods.size(), 2); @@ -47,7 +47,7 @@ R"( // cannot handle Qt resources. QTemporaryDir tempDir(QDir::tempPath() + u"/shiboken_testmodifydocXXXXXX"_s); QVERIFY2(tempDir.isValid(), qPrintable(tempDir.errorString())); - const QString docFileName = u"a.xml"_s; + constexpr auto docFileName = "a.xml"_L1; QVERIFY(QFile::copy(u":/"_s + docFileName, tempDir.filePath(docFileName))); QtDocParser docParser; @@ -95,9 +95,9 @@ void TestModifyDocumentation::testInjectAddedFunctionDocumentation() )XML"; QScopedPointer builder(TestUtil::parse(cppCode, xmlCode)); QVERIFY(builder); - const auto classA = AbstractMetaClass::findClass(builder->classes(), u"A"); + const auto classA = AbstractMetaClass::findClass(builder->classes(), "A"); QVERIFY(classA); - const auto f = classA->findFunction(u"foo"); + const auto f = classA->findFunction("foo"); QVERIFY(f); QVERIFY(f->isUserAdded()); auto docMods = f->addedFunctionDocModifications(); diff --git a/sources/shiboken6/ApiExtractor/tests/testmodifyfunction.cpp b/sources/shiboken6/ApiExtractor/tests/testmodifyfunction.cpp index c08f5a82..a7d40f70 100644 --- a/sources/shiboken6/ApiExtractor/tests/testmodifyfunction.cpp +++ b/sources/shiboken6/ApiExtractor/tests/testmodifyfunction.cpp @@ -48,8 +48,8 @@ void TestModifyFunction::testRenameArgument() QScopedPointer builder(TestUtil::parse(cppCode, xmlCode.constData(), false)); QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto classA = AbstractMetaClass::findClass(classes, u"A"); - const auto func = classA->findFunction(u"method"); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + const auto func = classA->findFunction("method"); QVERIFY(func); QCOMPARE(func->argumentName(1), u"otherArg"); @@ -76,8 +76,8 @@ void TestModifyFunction::testOwnershipTransfer() QScopedPointer builder(TestUtil::parse(cppCode, xmlCode, false)); QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto classB = AbstractMetaClass::findClass(classes, u"B"); - const auto func = classB->findFunction(u"method"); + const auto classB = AbstractMetaClass::findClass(classes, "B"); + const auto func = classB->findFunction("method"); QVERIFY(func); QCOMPARE(func->argumentTargetOwnership(func->ownerClass(), 0), @@ -126,45 +126,45 @@ void TestModifyFunction::invalidateAfterUse() false, u"0.1"_s)); QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto classB = AbstractMetaClass::findClass(classes, u"B"); - auto func = classB->findFunction(u"call"); + const auto classB = AbstractMetaClass::findClass(classes, "B"); + auto func = classB->findFunction("call"); QCOMPARE(func->modifications().size(), 1); QCOMPARE(func->modifications().at(0).argument_mods().size(), 1); QVERIFY(func->modifications().at(0).argument_mods().at(0).resetAfterUse()); - const auto classC = AbstractMetaClass::findClass(classes, u"C"); + const auto classC = AbstractMetaClass::findClass(classes, "C"); QVERIFY(classC); - func = classC->findFunction(u"call"); + func = classC->findFunction("call"); QCOMPARE(func->modifications().size(), 1); QCOMPARE(func->modifications().at(0).argument_mods().size(), 1); QVERIFY(func->modifications().at(0).argument_mods().at(0).resetAfterUse()); - func = classC->findFunction(u"call2"); + func = classC->findFunction("call2"); QCOMPARE(func->modifications().size(), 1); QCOMPARE(func->modifications().at(0).argument_mods().size(), 1); QVERIFY(func->modifications().at(0).argument_mods().at(0).resetAfterUse()); - AbstractMetaClassCPtr classD = AbstractMetaClass::findClass(classes, u"D"); + AbstractMetaClassCPtr classD = AbstractMetaClass::findClass(classes, "D"); QVERIFY(classD); - func = classD->findFunction(u"call"); + func = classD->findFunction("call"); QCOMPARE(func->modifications().size(), 1); QCOMPARE(func->modifications().at(0).argument_mods().size(), 1); QVERIFY(func->modifications().at(0).argument_mods().at(0).resetAfterUse()); - func = classD->findFunction(u"call2"); + func = classD->findFunction("call2"); QCOMPARE(func->modifications().size(), 1); QCOMPARE(func->modifications().at(0).argument_mods().size(), 1); QVERIFY(func->modifications().at(0).argument_mods().at(0).resetAfterUse()); - const auto classE = AbstractMetaClass::findClass(classes, u"E"); + const auto classE = AbstractMetaClass::findClass(classes, "E"); QVERIFY(classE); - func = classE->findFunction(u"call"); + func = classE->findFunction("call"); QVERIFY(func); QCOMPARE(func->modifications().size(), 1); QCOMPARE(func->modifications().at(0).argument_mods().size(), 1); QVERIFY(func->modifications().at(0).argument_mods().at(0).resetAfterUse()); - func = classE->findFunction(u"call2"); + func = classE->findFunction("call2"); QVERIFY(func); QCOMPARE(func->modifications().size(), 1); QCOMPARE(func->modifications().at(0).argument_mods().size(), 1); @@ -199,13 +199,13 @@ void TestModifyFunction::testWithApiVersion() false, u"0.1"_s)); QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto classB = AbstractMetaClass::findClass(classes, u"B"); - auto func = classB->findFunction(u"method"); + const auto classB = AbstractMetaClass::findClass(classes, "B"); + auto func = classB->findFunction("method"); auto returnOwnership = func->argumentTargetOwnership(func->ownerClass(), 0); QCOMPARE(returnOwnership, TypeSystem::CppOwnership); - func = classB->findFunction(u"methodB"); + func = classB->findFunction("methodB"); returnOwnership = func->argumentTargetOwnership(func->ownerClass(), 0); QVERIFY(returnOwnership != TypeSystem::CppOwnership); } @@ -238,31 +238,31 @@ struct A { false, u"0.1"_s)); QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto classA = AbstractMetaClass::findClass(classes, u"A"); + const auto classA = AbstractMetaClass::findClass(classes, "A"); QVERIFY(classA); // Nothing specified, true - const auto f1 = classA->findFunction(u"f1"); + const auto f1 = classA->findFunction("f1"); QVERIFY(f1); QVERIFY(!f1->allowThread()); // 'auto' specified, should be false for nontrivial function - const auto f2 = classA->findFunction(u"f2"); + const auto f2 = classA->findFunction("f2"); QVERIFY(f2); QVERIFY(f2->allowThread()); // 'no' specified, should be false - const auto f3 = classA->findFunction(u"f3"); + const auto f3 = classA->findFunction("f3"); QVERIFY(f3); QVERIFY(!f3->allowThread()); // Nothing specified, should be false for simple getter - const auto getter1 = classA->findFunction(u"getter1"); + const auto getter1 = classA->findFunction("getter1"); QVERIFY(getter1); QVERIFY(!getter1->allowThread()); // Forced to true simple getter - const auto getter2 = classA->findFunction(u"getter2"); + const auto getter2 = classA->findFunction("getter2"); QVERIFY(getter2); QVERIFY(getter2->allowThread()); // Forced to true simple getter } @@ -436,21 +436,21 @@ void TestModifyFunction::testScopedModifications() QScopedPointer builder(TestUtil::parse(cppCode.constData(), xmlCode.constData(), false)); QVERIFY(builder); - const auto classA = AbstractMetaClass::findClass(builder->classes(), u"A"); + const auto classA = AbstractMetaClass::findClass(builder->classes(), "A"); QVERIFY(classA); - auto f = classA->findFunction(QStringLiteral("unspecified")); + auto f = classA->findFunction("unspecified"); QVERIFY(f); QCOMPARE(f->exceptionSpecification(), ExceptionSpecification::Unknown); QCOMPARE(f->generateExceptionHandling(), expectedGenerateUnspecified); QCOMPARE(f->allowThread(), expectedAllowThread); - f = classA->findFunction(QStringLiteral("nonThrowing")); + f = classA->findFunction("nonThrowing"); QVERIFY(f); QCOMPARE(f->exceptionSpecification(), ExceptionSpecification::NoExcept); QCOMPARE(f->generateExceptionHandling(), expectedGenerateNonThrowing); - f = classA->findFunction(QStringLiteral("throwing")); + f = classA->findFunction("throwing"); QVERIFY(f); QCOMPARE(f->exceptionSpecification(), ExceptionSpecification::Throws); QCOMPARE(f->generateExceptionHandling(), expectedGenerateThrowing); @@ -458,20 +458,20 @@ void TestModifyFunction::testScopedModifications() void TestModifyFunction::testSnakeCaseRenaming_data() { - QTest::addColumn("name"); - QTest::addColumn("expected"); + QTest::addColumn("name"); + QTest::addColumn("expected"); QTest::newRow("s1") - << QStringLiteral("snakeCaseFunc") << QStringLiteral("snake_case_func"); + << "snakeCaseFunc"_L1 << "snake_case_func"_L1; QTest::newRow("s2") - << QStringLiteral("SnakeCaseFunc") << QStringLiteral("snake_case_func"); + << "SnakeCaseFunc"_L1 << "snake_case_func"_L1; QTest::newRow("consecutive-uppercase") - << QStringLiteral("snakeCAseFunc") << QStringLiteral("snakeCAseFunc"); + << "snakeCAseFunc"_L1 << "snakeCAseFunc"_L1; } void TestModifyFunction::testSnakeCaseRenaming() { - QFETCH(QString, name); - QFETCH(QString, expected); + QFETCH(QLatin1StringView, name); + QFETCH(QLatin1StringView, expected); const QString actual = AbstractMetaBuilder::getSnakeCaseName(name); QCOMPARE(actual, expected); diff --git a/sources/shiboken6/ApiExtractor/tests/testmultipleinheritance.cpp b/sources/shiboken6/ApiExtractor/tests/testmultipleinheritance.cpp index e47aa768..1cf4c8e0 100644 --- a/sources/shiboken6/ApiExtractor/tests/testmultipleinheritance.cpp +++ b/sources/shiboken6/ApiExtractor/tests/testmultipleinheritance.cpp @@ -35,7 +35,7 @@ void TestMultipleInheritance::testVirtualClass() AbstractMetaClassList classes = builder->classes(); QCOMPARE(classes.size(), 4); - const auto classD = AbstractMetaClass::findClass(classes, u"D"); + const auto classD = AbstractMetaClass::findClass(classes, "D"); bool functionFound = false; for (const auto &f : classD->functions()) { if (f->name() == u"theBug") { diff --git a/sources/shiboken6/ApiExtractor/tests/testnamespace.cpp b/sources/shiboken6/ApiExtractor/tests/testnamespace.cpp index 974dd0bd..3773e614 100644 --- a/sources/shiboken6/ApiExtractor/tests/testnamespace.cpp +++ b/sources/shiboken6/ApiExtractor/tests/testnamespace.cpp @@ -33,11 +33,11 @@ void NamespaceTest::testNamespaceMembers() QScopedPointer builder(TestUtil::parse(cppCode, xmlCode, false)); QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto ns = AbstractMetaClass::findClass(classes, u"Namespace"); + const auto ns = AbstractMetaClass::findClass(classes, "Namespace"); QVERIFY(ns); auto metaEnum = ns->findEnum(u"Option"_s); QVERIFY(metaEnum.has_value()); - const auto func = ns->findFunction(u"foo"); + const auto func = ns->findFunction("foo"); QVERIFY(func); } @@ -63,13 +63,13 @@ void NamespaceTest::testNamespaceInnerClassMembers() QScopedPointer builder(TestUtil::parse(cppCode, xmlCode, false)); QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto ons = AbstractMetaClass::findClass(classes, u"OuterNamespace"); + const auto ons = AbstractMetaClass::findClass(classes, "OuterNamespace"); QVERIFY(ons); - const auto ins = AbstractMetaClass::findClass(classes, u"OuterNamespace::InnerNamespace"); + const auto ins = AbstractMetaClass::findClass(classes, "OuterNamespace::InnerNamespace"); QVERIFY(ins); - const auto sc = AbstractMetaClass::findClass(classes, u"OuterNamespace::InnerNamespace::SomeClass"); + const auto sc = AbstractMetaClass::findClass(classes, "OuterNamespace::InnerNamespace::SomeClass"); QVERIFY(sc); - const auto meth = sc->findFunction(u"method"); + const auto meth = sc->findFunction("method"); QVERIFY(meth); } diff --git a/sources/shiboken6/ApiExtractor/tests/testnestedtypes.cpp b/sources/shiboken6/ApiExtractor/tests/testnestedtypes.cpp index 9af411b1..10ca1a0f 100644 --- a/sources/shiboken6/ApiExtractor/tests/testnestedtypes.cpp +++ b/sources/shiboken6/ApiExtractor/tests/testnestedtypes.cpp @@ -46,10 +46,10 @@ void TestNestedTypes::testNestedTypesModifications() QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto ons = AbstractMetaClass::findClass(classes, u"OuterNamespace"); + const auto ons = AbstractMetaClass::findClass(classes, "OuterNamespace"); QVERIFY(ons); - const auto ins = AbstractMetaClass::findClass(classes, u"OuterNamespace::InnerNamespace"); + const auto ins = AbstractMetaClass::findClass(classes, "OuterNamespace::InnerNamespace"); QVERIFY(ins); QCOMPARE(ins->functions().size(), 1); QCOMPARE(ins->typeEntry()->codeSnips().size(), 1); @@ -69,7 +69,7 @@ void TestNestedTypes::testNestedTypesModifications() QCOMPARE(snip.code().trimmed(), u"custom_code2();"); const auto sc = - AbstractMetaClass::findClass(classes, u"OuterNamespace::InnerNamespace::SomeClass"); + AbstractMetaClass::findClass(classes, "OuterNamespace::InnerNamespace::SomeClass"); QVERIFY(sc); QCOMPARE(sc->functions().size(), 2); // default constructor and removed method const auto removedFunc = sc->functions().constLast(); @@ -96,11 +96,11 @@ void TestNestedTypes::testDuplicationOfNestedTypes() QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); QCOMPARE(classes.size(), 2); - const auto nspace = AbstractMetaClass::findClass(classes, u"Namespace"); + const auto nspace = AbstractMetaClass::findClass(classes, "Namespace"); QVERIFY(nspace); - const auto cls1 = AbstractMetaClass::findClass(classes, u"SomeClass"); + const auto cls1 = AbstractMetaClass::findClass(classes, "SomeClass"); QVERIFY(cls1); - const auto cls2 = AbstractMetaClass::findClass(classes, u"Namespace::SomeClass"); + const auto cls2 = AbstractMetaClass::findClass(classes, "Namespace::SomeClass"); QVERIFY(cls2); QCOMPARE(cls1, cls2); QCOMPARE(cls1->name(), u"SomeClass"); diff --git a/sources/shiboken6/ApiExtractor/tests/testprimitivetypetag.cpp b/sources/shiboken6/ApiExtractor/tests/testprimitivetypetag.cpp index b941094f..99cced09 100644 --- a/sources/shiboken6/ApiExtractor/tests/testprimitivetypetag.cpp +++ b/sources/shiboken6/ApiExtractor/tests/testprimitivetypetag.cpp @@ -27,7 +27,7 @@ void TestPrimitiveTypeTag::testPrimitiveTypeDefaultConstructor() AbstractMetaClassList classes = builder->classes(); QCOMPARE(classes.size(), 1); - const auto classB = AbstractMetaClass::findClass(classes, u"B"); + const auto classB = AbstractMetaClass::findClass(classes, "B"); QVERIFY(classB); auto typeEntry = TypeDatabase::instance()->findPrimitiveType(u"A"_s); diff --git a/sources/shiboken6/ApiExtractor/tests/testrefcounttag.cpp b/sources/shiboken6/ApiExtractor/tests/testrefcounttag.cpp index 22f8b7a8..f2e26162 100644 --- a/sources/shiboken6/ApiExtractor/tests/testrefcounttag.cpp +++ b/sources/shiboken6/ApiExtractor/tests/testrefcounttag.cpp @@ -34,8 +34,8 @@ void TestRefCountTag::testReferenceCountTag() QScopedPointer builder(TestUtil::parse(cppCode, xmlCode, false)); QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto classB = AbstractMetaClass::findClass(classes, u"B"); - const auto func = classB->findFunction(u"keepObject"); + const auto classB = AbstractMetaClass::findClass(classes, "B"); + const auto func = classB->findFunction("keepObject"); QVERIFY(func); const auto refCount = func->modifications().constFirst().argument_mods().constFirst().referenceCounts().constFirst(); @@ -68,8 +68,8 @@ void TestRefCountTag::testWithApiVersion() false, u"0.1"_s)); QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto classB = AbstractMetaClass::findClass(classes, u"B"); - const auto func = classB->findFunction(u"keepObject"); + const auto classB = AbstractMetaClass::findClass(classes, "B"); + const auto func = classB->findFunction("keepObject"); QVERIFY(func); const auto refCount = func->modifications().constFirst().argument_mods().constFirst().referenceCounts().constFirst(); diff --git a/sources/shiboken6/ApiExtractor/tests/testreferencetopointer.cpp b/sources/shiboken6/ApiExtractor/tests/testreferencetopointer.cpp index e3b18758..ae85c5a8 100644 --- a/sources/shiboken6/ApiExtractor/tests/testreferencetopointer.cpp +++ b/sources/shiboken6/ApiExtractor/tests/testreferencetopointer.cpp @@ -25,9 +25,9 @@ void TestReferenceToPointer::testReferenceToPointerArgument() QScopedPointer builder(TestUtil::parse(cppCode, xmlCode, false)); QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto classB = AbstractMetaClass::findClass(classes, u"B"); + const auto classB = AbstractMetaClass::findClass(classes, "B"); QVERIFY(classB); - const auto func = classB->findFunction(u"dummy"); + const auto func = classB->findFunction("dummy"); QVERIFY(func); QCOMPARE(func->arguments().constFirst().type().minimalSignature(), u"A*&"); } diff --git a/sources/shiboken6/ApiExtractor/tests/testremovefield.cpp b/sources/shiboken6/ApiExtractor/tests/testremovefield.cpp index a1267585..2cc82071 100644 --- a/sources/shiboken6/ApiExtractor/tests/testremovefield.cpp +++ b/sources/shiboken6/ApiExtractor/tests/testremovefield.cpp @@ -4,10 +4,15 @@ #include "testremovefield.h" #include #include "testutil.h" +#include #include +#include +#include #include #include +using namespace Qt::StringLiterals; + void TestRemoveField::testRemoveField() { const char cppCode[] = "\ @@ -25,13 +30,47 @@ void TestRemoveField::testRemoveField() QScopedPointer builder(TestUtil::parse(cppCode, xmlCode, false)); QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto classA = AbstractMetaClass::findClass(classes, u"A"); + const auto classA = AbstractMetaClass::findClass(classes, "A"); QVERIFY(classA); QCOMPARE(classA->fields().size(), 1); const AbstractMetaField &fieldA = classA->fields().constFirst(); QCOMPARE(fieldA.name(), u"fieldA"); } +// Verify that 'static constexpr' fields are seen as static/const and +// appear fully qualified for function parameter default values. +void TestRemoveField::testConstExprField() +{ + const char cppCode[] = R"( +struct A { + static constexpr int constExprField = 44; + + void f(int iParam=constExprField); +}; +)"; + + const char xmlCode[] = R"( + + + +)"; + + QScopedPointer builder(TestUtil::parse(cppCode, xmlCode, false)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + QVERIFY(classA); + const auto &fields = classA->fields(); + QCOMPARE(fields.size(), 1); + QVERIFY(fields.constFirst().isStatic()); + QVERIFY(fields.constFirst().type().isConstant()); + const auto function = classA->findFunction("f"_L1); + QVERIFY(function); + const auto &arguments = function->arguments(); + QCOMPARE(arguments.size(), 1); + QCOMPARE(arguments.constFirst().defaultValueExpression(), "A::constExprField"_L1); +} + QTEST_APPLESS_MAIN(TestRemoveField) diff --git a/sources/shiboken6/ApiExtractor/tests/testremovefield.h b/sources/shiboken6/ApiExtractor/tests/testremovefield.h index febe672c..05912d99 100644 --- a/sources/shiboken6/ApiExtractor/tests/testremovefield.h +++ b/sources/shiboken6/ApiExtractor/tests/testremovefield.h @@ -11,6 +11,7 @@ class TestRemoveField : public QObject Q_OBJECT private slots: void testRemoveField(); + void testConstExprField(); }; #endif diff --git a/sources/shiboken6/ApiExtractor/tests/testremoveimplconv.cpp b/sources/shiboken6/ApiExtractor/tests/testremoveimplconv.cpp index e6c5eb0c..87e318e9 100644 --- a/sources/shiboken6/ApiExtractor/tests/testremoveimplconv.cpp +++ b/sources/shiboken6/ApiExtractor/tests/testremoveimplconv.cpp @@ -33,11 +33,11 @@ void TestRemoveImplConv::testRemoveImplConv() QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); QCOMPARE(classes.size(), 3); - const auto classA = AbstractMetaClass::findClass(classes, u"A"); + const auto classA = AbstractMetaClass::findClass(classes, "A"); QVERIFY(classA); - const auto classB = AbstractMetaClass::findClass(classes, u"B"); + const auto classB = AbstractMetaClass::findClass(classes, "B"); QVERIFY(classB); - const auto classC = AbstractMetaClass::findClass(classes, u"C"); + const auto classC = AbstractMetaClass::findClass(classes, "C"); QVERIFY(classC); const auto implConv = classC->implicitConversions(); QCOMPARE(implConv.size(), 1); diff --git a/sources/shiboken6/ApiExtractor/tests/testremoveoperatormethod.cpp b/sources/shiboken6/ApiExtractor/tests/testremoveoperatormethod.cpp index c2a91c8a..17a069b5 100644 --- a/sources/shiboken6/ApiExtractor/tests/testremoveoperatormethod.cpp +++ b/sources/shiboken6/ApiExtractor/tests/testremoveoperatormethod.cpp @@ -70,7 +70,7 @@ struct A { QScopedPointer builder(TestUtil::parse(cppCode, xmlCode, false)); QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto classA = AbstractMetaClass::findClass(classes, u"A"); + const auto classA = AbstractMetaClass::findClass(classes, "A"); QVERIFY(classA); QCOMPARE(classA->functions().size(), 14); QStringList removedSignatures; diff --git a/sources/shiboken6/ApiExtractor/tests/testresolvetype.cpp b/sources/shiboken6/ApiExtractor/tests/testresolvetype.cpp index 5e486ea5..67ebcc60 100644 --- a/sources/shiboken6/ApiExtractor/tests/testresolvetype.cpp +++ b/sources/shiboken6/ApiExtractor/tests/testresolvetype.cpp @@ -49,9 +49,9 @@ void TestResolveType::testResolveReturnTypeFromParentScope() QScopedPointer builder(TestUtil::parse(cppCode, xmlCode, false)); QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto classD = AbstractMetaClass::findClass(classes, u"A::D"); + const auto classD = AbstractMetaClass::findClass(classes, "A::D"); QVERIFY(classD); - const auto meth = classD->findFunction(u"method"); + const auto meth = classD->findFunction("method"); QVERIFY(meth); QVERIFY(meth); } @@ -125,7 +125,7 @@ public: fixture->classType = AbstractMetaType(fixture->klass->typeEntry()); fixture->classType.decideUsagePattern(); - for (const auto &f : fixture->klass->findFunctions(u"Test"_s)) { + for (const auto &f : fixture->klass->findFunctions("Test")) { if (f->functionType() == AbstractMetaFunction::ConstructorFunction && f->arguments().size() == 1) { const auto type = f->arguments().constFirst().type(); @@ -138,7 +138,7 @@ public: if (fixture->intType.isVoid() || fixture->stringType.isVoid()) return -3; - auto listFunc = fixture->klass->findFunction(u"listFunc"_s); + auto listFunc = fixture->klass->findFunction("listFunc"); if (!listFunc || listFunc->arguments().size() != 1) return -3; fixture->listType = listFunc->arguments().constFirst().type(); @@ -265,7 +265,7 @@ public: QScopedPointer builder(TestUtil::parse(cppCode, xmlCode, false)); QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto testClass = AbstractMetaClass::findClass(classes, u"Test"); + const auto testClass = AbstractMetaClass::findClass(classes, "Test"); QVERIFY(testClass); auto *tdb = TypeDatabase::instance(); diff --git a/sources/shiboken6/ApiExtractor/tests/testreverseoperators.cpp b/sources/shiboken6/ApiExtractor/tests/testreverseoperators.cpp index fcbdbc4e..f4eecff2 100644 --- a/sources/shiboken6/ApiExtractor/tests/testreverseoperators.cpp +++ b/sources/shiboken6/ApiExtractor/tests/testreverseoperators.cpp @@ -27,7 +27,7 @@ void TestReverseOperators::testReverseSum() QScopedPointer builder(TestUtil::parse(cppCode, xmlCode, false)); QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto classA = AbstractMetaClass::findClass(classes, u"A"); + const auto classA = AbstractMetaClass::findClass(classes, "A"); QVERIFY(classA); QCOMPARE(classA->functions().size(), 4); @@ -69,11 +69,11 @@ void TestReverseOperators::testReverseSumWithAmbiguity() QScopedPointer builder(TestUtil::parse(cppCode, xmlCode, false)); QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto classA = AbstractMetaClass::findClass(classes, u"A"); + const auto classA = AbstractMetaClass::findClass(classes, "A"); QVERIFY(classA); QCOMPARE(classA->functions().size(), 4); - const auto classB = AbstractMetaClass::findClass(classes, u"B"); + const auto classB = AbstractMetaClass::findClass(classes, "B"); QVERIFY(classB); QCOMPARE(classB->functions().size(), 4); @@ -114,7 +114,7 @@ void TestReverseOperators::testSpaceshipOperator() QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); QCOMPARE(classes.size(), 1); - const auto testClass = AbstractMetaClass::findClass(classes, u"Test"); + const auto testClass = AbstractMetaClass::findClass(classes, "Test"); QVERIFY(testClass); const auto &functions = testClass->functions(); // 6 operators should be synthesized diff --git a/sources/shiboken6/ApiExtractor/tests/testtemplates.cpp b/sources/shiboken6/ApiExtractor/tests/testtemplates.cpp index eba2ecea..ea37c625 100644 --- a/sources/shiboken6/ApiExtractor/tests/testtemplates.cpp +++ b/sources/shiboken6/ApiExtractor/tests/testtemplates.cpp @@ -58,9 +58,9 @@ namespace Internet { QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto classB = AbstractMetaClass::findClass(classes, u"Bookmarks"); + const auto classB = AbstractMetaClass::findClass(classes, "Bookmarks"); QVERIFY(classB); - const auto func = classB->findFunction(u"list"); + const auto func = classB->findFunction("list"); QVERIFY(func); AbstractMetaType funcType = func->type(); QVERIFY(!funcType.isVoid()); @@ -96,11 +96,11 @@ namespace Namespace { QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto classB = AbstractMetaClass::findClass(classes, u"B"); + const auto classB = AbstractMetaClass::findClass(classes, "B"); QVERIFY(classB); QVERIFY(!classB->baseClass()); QVERIFY(classB->baseClassName().isEmpty()); - const auto func = classB->findFunction(u"foo"); + const auto func = classB->findFunction("foo"); QVERIFY(func); AbstractMetaType argType = func->arguments().constFirst().type(); QCOMPARE(argType.instantiations().size(), 1); @@ -215,12 +215,12 @@ struct List { QCOMPARE(templates.size(), 1); AbstractMetaClassCPtr list = templates.constFirst(); // Verify that the parameter of "void append(List l)" gets fixed to "List" - const auto append = list->findFunction(QStringLiteral("append")); + const auto append = list->findFunction("append"); QVERIFY(append); QCOMPARE(append->arguments().size(), 1); QCOMPARE(append->arguments().at(0).type().cppSignature(), u"List"); // Verify that the parameter of "void erase(Iterator)" is not modified - const auto erase = list->findFunction(QStringLiteral("erase")); + const auto erase = list->findFunction("erase"); QVERIFY(erase); QCOMPARE(erase->arguments().size(), 1); QCOMPARE(erase->arguments().at(0).type().cppSignature(), u"List::Iterator"); @@ -255,7 +255,7 @@ struct FooBars : public ListContainer {}; QCOMPARE(classes.size(), 2); QCOMPARE(templates.size(), 1); - const auto foobars = AbstractMetaClass::findClass(classes, u"FooBars"); + const auto foobars = AbstractMetaClass::findClass(classes, "FooBars"); QCOMPARE(foobars->functions().size(), 4); AbstractMetaClassCPtr lc = templates.constFirst(); @@ -289,7 +289,7 @@ template struct Future {}; QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto classB = AbstractMetaClass::findClass(classes, u"B"); + const auto classB = AbstractMetaClass::findClass(classes, "B"); QVERIFY(classB); QVERIFY(!classB->baseClass()); QVERIFY(classB->baseClassName().isEmpty()); @@ -328,7 +328,7 @@ template struct Future {}; QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto classB = AbstractMetaClass::findClass(classes, u"Namespace::B"); + const auto classB = AbstractMetaClass::findClass(classes, "Namespace::B"); QVERIFY(classB); QVERIFY(!classB->baseClass()); QVERIFY(classB->baseClassName().isEmpty()); @@ -365,9 +365,9 @@ typedef BaseTemplateClass TypeOneClass; AbstractMetaClassList classes = builder->classes(); QCOMPARE(classes.size(), 3); - const auto base = AbstractMetaClass::findClass(classes, u"BaseTemplateClass"); + const auto base = AbstractMetaClass::findClass(classes, "BaseTemplateClass"); QVERIFY(base); - const auto one = AbstractMetaClass::findClass(classes, u"TypeOneClass"); + const auto one = AbstractMetaClass::findClass(classes, "TypeOneClass"); QVERIFY(one); QCOMPARE(one->templateBaseClass(), base); QCOMPARE(one->functions().size(), base->functions().size()); @@ -414,7 +414,7 @@ typedef Vector IntVector; AbstractMetaClassList classes = builder->classes(); QCOMPARE(classes.size(), 1); - const auto vector = AbstractMetaClass::findClass(classes, u"IntVector"); + const auto vector = AbstractMetaClass::findClass(classes, "IntVector"); QVERIFY(vector); auto baseContainer = vector->typeEntry()->baseContainerType(); QVERIFY(baseContainer); @@ -422,11 +422,11 @@ typedef Vector IntVector; ContainerTypeEntry::ListContainer); QCOMPARE(vector->functions().size(), 4); - const auto method = vector->findFunction(u"method"); + const auto method = vector->findFunction("method"); QVERIFY(method); QCOMPARE(method->signature(), u"method(const Vector & vector)"); - const auto otherMethod = vector->findFunction(u"otherMethod"); + const auto otherMethod = vector->findFunction("otherMethod"); QVERIFY(otherMethod); QCOMPARE(otherMethod->signature(), u"otherMethod()"); QVERIFY(!otherMethod->type().isVoid()); @@ -540,26 +540,26 @@ void TestTemplates::testTemplateTypeDefs() QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto optional = AbstractMetaClass::findClass(classes, u"Optional"); + const auto optional = AbstractMetaClass::findClass(classes, "Optional"); QVERIFY(optional); // Find the typedef'ed class - const auto optionalInt = AbstractMetaClass::findClass(classes, u"IntOptional"); + const auto optionalInt = AbstractMetaClass::findClass(classes, "IntOptional"); QVERIFY(optionalInt); QCOMPARE(optionalInt->templateBaseClass(), optional); // Find the class typedef'ed in the typesystem XML - const auto xmlOptionalInt = AbstractMetaClass::findClass(classes, u"XmlIntOptional"); + const auto xmlOptionalInt = AbstractMetaClass::findClass(classes, "XmlIntOptional"); QVERIFY(xmlOptionalInt); QCOMPARE(xmlOptionalInt->templateBaseClass(), optional); // Check whether the value() method now has an 'int' return - const auto valueMethod = optionalInt->findFunction(u"value"); + const auto valueMethod = optionalInt->findFunction("value"); QVERIFY(valueMethod); QCOMPARE(valueMethod->type().cppSignature(), u"int"); // ditto for typesystem XML - const auto xmlValueMethod = xmlOptionalInt->findFunction(u"value"); + const auto xmlValueMethod = xmlOptionalInt->findFunction("value"); QVERIFY(xmlValueMethod); QCOMPARE(xmlValueMethod->type().cppSignature(), u"int"); @@ -609,7 +609,7 @@ public: QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto testClass = AbstractMetaClass::findClass(classes, u"Test"); + const auto testClass = AbstractMetaClass::findClass(classes, "Test"); QVERIFY(testClass); auto fields = testClass->fields(); @@ -618,7 +618,7 @@ public: QCOMPARE(fieldType.name(), u"Container1"); QCOMPARE(fieldType.instantiations().size(), 1); - const auto derived = AbstractMetaClass::findClass(classes, u"Derived"); + const auto derived = AbstractMetaClass::findClass(classes, "Derived"); QVERIFY(derived); auto base = derived->templateBaseClass(); QVERIFY(base); diff --git a/sources/shiboken6/ApiExtractor/tests/testtyperevision.cpp b/sources/shiboken6/ApiExtractor/tests/testtyperevision.cpp index a7c83907..72dae8cc 100644 --- a/sources/shiboken6/ApiExtractor/tests/testtyperevision.cpp +++ b/sources/shiboken6/ApiExtractor/tests/testtyperevision.cpp @@ -32,13 +32,13 @@ void TestTypeRevision::testRevisionAttr() QScopedPointer builder(TestUtil::parse(cppCode, xmlCode)); QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto rev0 = AbstractMetaClass::findClass(classes, u"Rev_0"); + const auto rev0 = AbstractMetaClass::findClass(classes, "Rev_0"); QCOMPARE(rev0->typeEntry()->revision(), 0); - const auto rev1 = AbstractMetaClass::findClass(classes, u"Rev_1"); + const auto rev1 = AbstractMetaClass::findClass(classes, "Rev_1"); QCOMPARE(rev1->typeEntry()->revision(), 1); - const auto rev2 = AbstractMetaClass::findClass(classes, u"Rev_2"); + const auto rev2 = AbstractMetaClass::findClass(classes, "Rev_2"); QCOMPARE(rev2->typeEntry()->revision(), 2); auto rev3 = rev2->findEnum(u"Rev_3"_s); diff --git a/sources/shiboken6/ApiExtractor/tests/testutil.h b/sources/shiboken6/ApiExtractor/tests/testutil.h index 347143b7..dc4e3b2d 100644 --- a/sources/shiboken6/ApiExtractor/tests/testutil.h +++ b/sources/shiboken6/ApiExtractor/tests/testutil.h @@ -27,7 +27,7 @@ namespace TestUtil auto *td = TypeDatabase::instance(true); if (apiVersion.isEmpty()) TypeDatabase::clearApiVersions(); - else if (!TypeDatabase::setApiVersion(QStringLiteral("*"), apiVersion)) + else if (!TypeDatabase::setApiVersion(QLatin1StringView("*"), apiVersion)) return nullptr; td->setDropTypeEntries(dropTypeEntries); QBuffer buffer; @@ -39,7 +39,7 @@ namespace TestUtil return nullptr; buffer.close(); // parse C++ code - QTemporaryFile tempSource(QDir::tempPath() + QStringLiteral("/st_XXXXXX_main.cpp")); + QTemporaryFile tempSource(QDir::tempPath() + QLatin1StringView("/st_XXXXXX_main.cpp")); if (!tempSource.open()) { qWarning().noquote().nospace() << "Creation of temporary file failed: " << tempSource.errorString(); diff --git a/sources/shiboken6/ApiExtractor/tests/testvaluetypedefaultctortag.cpp b/sources/shiboken6/ApiExtractor/tests/testvaluetypedefaultctortag.cpp index 19143e89..98e30eac 100644 --- a/sources/shiboken6/ApiExtractor/tests/testvaluetypedefaultctortag.cpp +++ b/sources/shiboken6/ApiExtractor/tests/testvaluetypedefaultctortag.cpp @@ -26,12 +26,12 @@ void TestValueTypeDefaultCtorTag::testValueTypeDefaultCtorTagArgument() AbstractMetaClassList classes = builder->classes(); - const auto classA = AbstractMetaClass::findClass(classes, u"A"); + const auto classA = AbstractMetaClass::findClass(classes, "A"); QVERIFY(classA); QVERIFY(classA->typeEntry()->hasDefaultConstructor()); QCOMPARE(classA->typeEntry()->defaultConstructor(), u"A(0, 0)"); - const auto classB = AbstractMetaClass::findClass(classes, u"B"); + const auto classB = AbstractMetaClass::findClass(classes, "B"); QVERIFY(classB); QVERIFY(!classB->typeEntry()->hasDefaultConstructor()); } diff --git a/sources/shiboken6/ApiExtractor/tests/testvoidarg.cpp b/sources/shiboken6/ApiExtractor/tests/testvoidarg.cpp index b472cbfb..a600181a 100644 --- a/sources/shiboken6/ApiExtractor/tests/testvoidarg.cpp +++ b/sources/shiboken6/ApiExtractor/tests/testvoidarg.cpp @@ -19,9 +19,9 @@ void TestVoidArg::testVoidParsedFunction() QScopedPointer builder(TestUtil::parse(cppCode, xmlCode)); QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto classA = AbstractMetaClass::findClass(classes, u"A"); + const auto classA = AbstractMetaClass::findClass(classes, "A"); QVERIFY(classA); - const auto addedFunc = classA->findFunction(u"a"); + const auto addedFunc = classA->findFunction("a"); QVERIFY(addedFunc); QCOMPARE(addedFunc->arguments().size(), 0); } @@ -38,9 +38,9 @@ void TestVoidArg::testVoidAddedFunction() QScopedPointer builder(TestUtil::parse(cppCode, xmlCode)); QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto classA = AbstractMetaClass::findClass(classes, u"A"); + const auto classA = AbstractMetaClass::findClass(classes, "A"); QVERIFY(classA); - const auto addedFunc = classA->findFunction(u"a"); + const auto addedFunc = classA->findFunction("a"); QVERIFY(addedFunc); QCOMPARE(addedFunc->arguments().size(), 0); @@ -56,9 +56,9 @@ void TestVoidArg::testVoidPointerParsedFunction() QScopedPointer builder(TestUtil::parse(cppCode, xmlCode)); QVERIFY(builder); AbstractMetaClassList classes = builder->classes(); - const auto classA = AbstractMetaClass::findClass(classes, u"A"); + const auto classA = AbstractMetaClass::findClass(classes, "A"); QVERIFY(classA); - const auto addedFunc = classA->findFunction(u"a"); + const auto addedFunc = classA->findFunction("a"); QVERIFY(addedFunc); QCOMPARE(addedFunc->arguments().size(), 1); diff --git a/sources/shiboken6/ApiExtractor/textstream.h b/sources/shiboken6/ApiExtractor/textstream.h index d7199b9b..228f3640 100644 --- a/sources/shiboken6/ApiExtractor/textstream.h +++ b/sources/shiboken6/ApiExtractor/textstream.h @@ -77,6 +77,10 @@ public: void putRawChar(char c) { m_str << c; } TextStream &operator<<(QStringView v) { putString(v); return *this; } + TextStream &operator<<(const QString &qs) { putString(QStringView{qs}); return *this; } + TextStream &operator<<(QLatin1StringView lv) { putString(lv.constData()); return *this; } + TextStream &operator<<(QUtf8StringView uv) { putString(uv.data()); return *this; } + TextStream &operator<<(const QByteArray &ba) { putString(ba.constData()); return *this; } TextStream &operator<<(QChar c) { putChar(c); return *this; } TextStream &operator<<(const char *s) { putString(s); return *this; } TextStream &operator<<(char c) { putChar(c); return *this; } @@ -140,6 +144,12 @@ void rstCodeOff(TextStream &s); void rstItalic(TextStream &s); void rstItalicOff(TextStream &s); +inline TextStream &operator<<(TextStream &str, QAnyStringView asv) +{ + asv.visit([&str](auto s) { str << s; }); + return str; +} + /// Format an aligned field template class AlignedField diff --git a/sources/shiboken6/ApiExtractor/typedatabase.cpp b/sources/shiboken6/ApiExtractor/typedatabase.cpp index 41bd0b0b..749c4baa 100644 --- a/sources/shiboken6/ApiExtractor/typedatabase.cpp +++ b/sources/shiboken6/ApiExtractor/typedatabase.cpp @@ -53,7 +53,7 @@ using TypeDatabaseParserContextPtr = std::shared_ptr; static QString wildcardToRegExp(QString w) { w.replace(u'?', u'.'); - w.replace(u'*', QStringLiteral(".*")); + w.replace(u'*', ".*"_L1); return w; } @@ -163,7 +163,8 @@ bool TypeDatabaseOptionsParser::handleBoolOption(const QString &key, OptionSourc break; case OptionSource::CommandLineSingleDash: if (key.startsWith(u'T')) { // "-T/path" ends up a bool option - m_options->m_typesystemPaths += key.sliced(1).split(QDir::listSeparator()); + m_options->m_typesystemPaths += key.sliced(1).split(QDir::listSeparator(), + Qt::SkipEmptyParts); return true; } break; @@ -201,12 +202,14 @@ bool TypeDatabaseOptionsParser::handleOption(const QString &key, const QString & } if (key == u"typesystem-paths") { - m_options->m_typesystemPaths += value.split(QDir::listSeparator()); + m_options->m_typesystemPaths += value.split(QDir::listSeparator(), + Qt::SkipEmptyParts); return true; } if (key == u"force-process-system-include-paths") { - m_options->m_forceProcessSystemIncludes += value.split(QDir::listSeparator()); + m_options->m_forceProcessSystemIncludes += value.split(QDir::listSeparator(), + Qt::SkipEmptyParts); return true; } @@ -285,7 +288,8 @@ TypeDatabase::TypeDatabase() : d(new TypeDatabasePrivate) // Environment TYPESYSTEMPATH if (qEnvironmentVariableIsSet(ENV_TYPESYSTEMPATH)) { d->m_typesystemPaths - += qEnvironmentVariable(ENV_TYPESYSTEMPATH).split(QDir::listSeparator()); + += qEnvironmentVariable(ENV_TYPESYSTEMPATH).split(QDir::listSeparator(), + Qt::SkipEmptyParts); } d->addBuiltInType(TypeEntryPtr(new VoidTypeEntry())); @@ -333,12 +337,11 @@ static const IntTypeNormalizationEntries &intTypeNormalizationEntries() static bool firstTime = true; if (firstTime) { firstTime = false; - for (auto t : {"char", "short", "int", "long"}) { - const QString intType = QLatin1StringView(t); + for (const auto &intType : {"char"_L1, "short"_L1, "int"_L1, "long"_L1}) { if (!TypeDatabase::instance()->findType(u'u' + intType)) { IntTypeNormalizationEntry entry; - entry.replacement = QStringLiteral("unsigned ") + intType; - entry.regex.setPattern(QStringLiteral("\\bu") + intType + QStringLiteral("\\b")); + entry.replacement = "unsigned "_L1 + intType; + entry.regex.setPattern("\\bu"_L1 + intType + "\\b"_L1); Q_ASSERT(entry.regex.isValid()); result.append(entry); } @@ -428,7 +431,7 @@ QStringList TypeDatabase::typesystemKeywords() const { QStringList result = d->m_typesystemKeywords; for (const auto &d : d->m_dropTypeEntries) - result.append(QStringLiteral("no_") + d); + result.append("no_"_L1 + d); switch (clang::emulatedCompilerLanguageLevel()) { case LanguageLevel::Cpp11: @@ -914,7 +917,7 @@ bool TypeDatabase::addSuppressedWarning(const QString &warning, bool generate, qsizetype lastPos = 0; for (qsizetype a = 0, aSize = asteriskPositions.size(); a < aSize; ++a) { if (a) - pattern.append(QStringLiteral(".*")); + pattern.append(".*"_L1); const auto nextPos = asteriskPositions.at(a); if (nextPos > lastPos) pattern.append(QRegularExpression::escape(warning.mid(lastPos, nextPos - lastPos))); @@ -941,7 +944,7 @@ bool TypeDatabase::isSuppressedWarning(QStringView s) const return false; auto wit = std::find_if(d->m_suppressedWarnings.cbegin(), d->m_suppressedWarnings.cend(), [&s] (const SuppressedWarning &e) { - return e.pattern.match(s).hasMatch(); + return e.pattern.matchView(s).hasMatch(); }); const bool found = wit != d->m_suppressedWarnings.cend(); if (found) @@ -1632,13 +1635,13 @@ void TypeDatabasePrivate::addBuiltInPrimitiveTypes() Q_ASSERT(pyUnicodeEntry && pyUnicodeEntry->isCustom()); auto pyUnicodeCustomEntry = std::static_pointer_cast(pyUnicodeEntry); - const QString stdString = u"std::string"_s; + constexpr auto stdString = "std::string"_L1; if (!m_entries.contains(stdString)) { addBuiltInCppStringPrimitiveType(stdString, u"std::string_view"_s, root, rootPackage, pyUnicodeCustomEntry); } - const QString stdWString = u"std::wstring"_s; + constexpr auto stdWString = "std::wstring"_L1; if (!m_entries.contains(stdWString)) { addBuiltInCppStringPrimitiveType(stdWString, u"std::wstring_view"_s, root, rootPackage, diff --git a/sources/shiboken6/ApiExtractor/typeparser.cpp b/sources/shiboken6/ApiExtractor/typeparser.cpp index d0884301..11d7bf64 100644 --- a/sources/shiboken6/ApiExtractor/typeparser.cpp +++ b/sources/shiboken6/ApiExtractor/typeparser.cpp @@ -147,8 +147,8 @@ Scanner::Token Scanner::nextToken(QString *errorMessage) QString Scanner::msgParseError(const QString &why) const { - return QStringLiteral("TypeParser: Unable to parse \"") - + QString(m_chars, m_length) + QStringLiteral("\": ") + why; + return "TypeParser: Unable to parse \""_L1 + + QString(m_chars, m_length) + "\": "_L1 + why; } TypeInfo TypeParser::parse(const QString &str, QString *errorMessage) @@ -199,7 +199,7 @@ TypeInfo TypeParser::parse(const QString &str, QString *errorMessage) stack.top().setReferenceType(RValueReference); break; case RValueReference: - const QString message = scanner.msgParseError(QStringLiteral("Too many '&' qualifiers")); + const QString message = scanner.msgParseError("Too many '&' qualifiers"_L1); if (errorMessage) *errorMessage = message; else @@ -246,7 +246,7 @@ TypeInfo TypeParser::parse(const QString &str, QString *errorMessage) case Scanner::OpenParenToken: // function pointers not supported case Scanner::CloseParenToken: { - const QString message = scanner.msgParseError(QStringLiteral("Function pointers are not supported")); + const QString message = scanner.msgParseError("Function pointers are not supported"_L1); if (errorMessage) *errorMessage = message; else diff --git a/sources/shiboken6/ApiExtractor/typesystem.cpp b/sources/shiboken6/ApiExtractor/typesystem.cpp index e5b3b63a..99d42b66 100644 --- a/sources/shiboken6/ApiExtractor/typesystem.cpp +++ b/sources/shiboken6/ApiExtractor/typesystem.cpp @@ -666,6 +666,9 @@ public: CodeSnipList m_codeSnips; TypeSystem::SnakeCase m_snakeCase = TypeSystem::SnakeCase::Disabled; + QString m_subModuleOf; + QString m_namespaceBegin; + QString m_namespaceEnd; }; TypeSystemTypeEntry::TypeSystemTypeEntry(const QString &entryName, const QVersionNumber &vr, @@ -703,6 +706,42 @@ void TypeSystemTypeEntry::addCodeSnip(const CodeSnip &codeSnip) d->m_codeSnips.append(codeSnip); } +QString TypeSystemTypeEntry::subModuleOf() const +{ + S_D(const TypeSystemTypeEntry); + return d->m_subModuleOf; +} + +void TypeSystemTypeEntry::setSubModule(const QString &s) +{ + S_D(TypeSystemTypeEntry); + d->m_subModuleOf = s; +} + +const QString &TypeSystemTypeEntry::namespaceBegin() const +{ + S_D(const TypeSystemTypeEntry); + return d->m_namespaceBegin; +} + +void TypeSystemTypeEntry::setNamespaceBegin(const QString &p) +{ + S_D(TypeSystemTypeEntry); + d->m_namespaceBegin = p; +} + +const QString &TypeSystemTypeEntry::namespaceEnd() const +{ + S_D(const TypeSystemTypeEntry); + return d->m_namespaceEnd; +} + +void TypeSystemTypeEntry::setNamespaceEnd(const QString &n) +{ + S_D(TypeSystemTypeEntry); + d->m_namespaceEnd = n; +} + TypeSystem::SnakeCase TypeSystemTypeEntry::snakeCase() const { S_D(const TypeSystemTypeEntry); @@ -1017,6 +1056,7 @@ public: QStringList m_rejectedEnums; FlagsTypeEntryPtr m_flags; QString m_cppType; + QString m_docFile; TypeSystem::PythonEnumType m_pythonEnumType = TypeSystem::PythonEnumType::Unspecified; }; @@ -1110,6 +1150,18 @@ QStringList EnumTypeEntry::enumValueRejections() const return d->m_rejectedEnums; } +QString EnumTypeEntry::docFile() const +{ + S_D(const EnumTypeEntry); + return d->m_docFile; +} + +void EnumTypeEntry::setDocFile(const QString &df) +{ + S_D(EnumTypeEntry); + d->m_docFile = df; +} + TypeEntry *EnumTypeEntry::clone() const { S_D(const EnumTypeEntry); @@ -1741,6 +1793,7 @@ void ComplexTypeEntry::useAsTypedef(const ComplexTypeEntryCPtr &source) TypeEntry::useAsTypedef(source); d->m_qualifiedCppName = source->qualifiedCppName(); d->m_targetType = source->targetType(); + d->m_typeFlags.setFlag(ComplexTypeEntry::Typedef); } ComplexTypeEntry::ComplexTypeEntry(ComplexTypeEntryPrivate *d) : @@ -2317,6 +2370,7 @@ public: } QStringList m_signatures; + QString m_docFile; }; FunctionTypeEntry::FunctionTypeEntry(const QString &entryName, const QString &signature, @@ -2344,6 +2398,18 @@ bool FunctionTypeEntry::hasSignature(const QString &signature) const return d->m_signatures.contains(signature); } +QString FunctionTypeEntry::docFile() const +{ + S_D(const FunctionTypeEntry); + return d->m_docFile; +} + +void FunctionTypeEntry::setDocFile(const QString &df) +{ + S_D(FunctionTypeEntry); + d->m_docFile = df; +} + TypeEntry *FunctionTypeEntry::clone() const { S_D(const FunctionTypeEntry); diff --git a/sources/shiboken6/ApiExtractor/typesystem_enums.h b/sources/shiboken6/ApiExtractor/typesystem_enums.h index 81304e6c..9ecbb08a 100644 --- a/sources/shiboken6/ApiExtractor/typesystem_enums.h +++ b/sources/shiboken6/ApiExtractor/typesystem_enums.h @@ -35,6 +35,7 @@ enum CodeSnipPosition { CodeSnipPositionBeginning, CodeSnipPositionEnd, CodeSnipPositionDeclaration, + CodeSnipPositionPyOverride, CodeSnipPositionAny }; diff --git a/sources/shiboken6/ApiExtractor/typesystem_typedefs.h b/sources/shiboken6/ApiExtractor/typesystem_typedefs.h index 59bb452a..5a4e12ff 100644 --- a/sources/shiboken6/ApiExtractor/typesystem_typedefs.h +++ b/sources/shiboken6/ApiExtractor/typesystem_typedefs.h @@ -68,6 +68,7 @@ using TypedefEntryCPtr = std::shared_ptr; using TypeSystemTypeEntryCPtr = std::shared_ptr; using ValueTypeEntryCPtr = std::shared_ptr; +using ComplexTypeEntryCList = QList; using ContainerTypeEntryCList = QList; using NamespaceTypeEntryList = QList; using PrimitiveTypeEntryCList = QList; diff --git a/sources/shiboken6/ApiExtractor/typesystemparser.cpp b/sources/shiboken6/ApiExtractor/typesystemparser.cpp index 3615710a..2b686e99 100644 --- a/sources/shiboken6/ApiExtractor/typesystemparser.cpp +++ b/sources/shiboken6/ApiExtractor/typesystemparser.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "typesystemparser_p.h" +#include "anystringview_helpers.h" #include "addedfunction.h" #include "codesnip.h" #include "enumtypeentry.h" @@ -45,78 +46,84 @@ using namespace Qt::StringLiterals; -static inline QString allowThreadAttribute() { return QStringLiteral("allow-thread"); } -static inline QString checkFunctionAttribute() { return QStringLiteral("check-function"); } -static inline QString copyableAttribute() { return QStringLiteral("copyable"); } -static inline QString accessAttribute() { return QStringLiteral("access"); } -static inline QString actionAttribute() { return QStringLiteral("action"); } -static inline QString quoteAfterLineAttribute() { return QStringLiteral("quote-after-line"); } -static inline QString quoteBeforeLineAttribute() { return QStringLiteral("quote-before-line"); } -static inline QString textAttribute() { return QStringLiteral("text"); } -static inline QString nameAttribute() { return QStringLiteral("name"); } -static inline QString sinceAttribute() { return QStringLiteral("since"); } -static inline QString untilAttribute() { return QStringLiteral("until"); } -static inline QString defaultSuperclassAttribute() { return QStringLiteral("default-superclass"); } -static inline QString deleteInMainThreadAttribute() { return QStringLiteral("delete-in-main-thread"); } -static inline QString deprecatedAttribute() { return QStringLiteral("deprecated"); } -static inline QString disableWrapperAttribute() { return QStringLiteral("disable-wrapper"); } -static inline QString exceptionHandlingAttribute() { return QStringLiteral("exception-handling"); } -static inline QString extensibleAttribute() { return QStringLiteral("extensible"); } -static inline QString fileNameAttribute() { return QStringLiteral("file-name"); } -static inline QString flagsAttribute() { return QStringLiteral("flags"); } -static inline QString forceAbstractAttribute() { return QStringLiteral("force-abstract"); } -static inline QString forceIntegerAttribute() { return QStringLiteral("force-integer"); } -static inline QString formatAttribute() { return QStringLiteral("format"); } -static inline QString generateUsingAttribute() { return QStringLiteral("generate-using"); } -static inline QString generateFunctionsAttribute() { return QStringLiteral("generate-functions"); } -static inline QString classAttribute() { return QStringLiteral("class"); } -static inline QString generateAttribute() { return QStringLiteral("generate"); } -static inline QString generateGetSetDefAttribute() { return QStringLiteral("generate-getsetdef"); } -static inline QString genericClassAttribute() { return QStringLiteral("generic-class"); } -static inline QString indexAttribute() { return QStringLiteral("index"); } -static inline QString invalidateAfterUseAttribute() { return QStringLiteral("invalidate-after-use"); } -static inline QString isNullAttribute() { return QStringLiteral("isNull"); } -static inline QString locationAttribute() { return QStringLiteral("location"); } -static inline QString modifiedTypeAttribute() { return QStringLiteral("modified-type"); } -static inline QString opaqueContainerAttribute() { return QStringLiteral("opaque-containers"); } -static inline QString operatorBoolAttribute() { return QStringLiteral("operator-bool"); } -static inline QString parentManagementAttribute() { return QStringLiteral("parent-management"); } -static inline QString pyiTypeAttribute() { return QStringLiteral("pyi-type"); } -static inline QString overloadNumberAttribute() { return QStringLiteral("overload-number"); } -static inline QString ownershipAttribute() { return QStringLiteral("owner"); } -static inline QString packageAttribute() { return QStringLiteral("package"); } -static inline QString positionAttribute() { return QStringLiteral("position"); } -static inline QString preferredConversionAttribute() { return QStringLiteral("preferred-conversion"); } -static inline QString preferredTargetLangTypeAttribute() { return QStringLiteral("preferred-target-lang-type"); } -static inline QString pythonEnumTypeAttribute() { return QStringLiteral("python-type"); } -static inline QString cppEnumTypeAttribute() { return QStringLiteral("cpp-type"); } -static inline QString qtMetaTypeAttribute() { return QStringLiteral("qt-register-metatype"); } -static inline QString removeAttribute() { return QStringLiteral("remove"); } -static inline QString renameAttribute() { return QStringLiteral("rename"); } -static inline QString readAttribute() { return QStringLiteral("read"); } -static inline QString targetLangNameAttribute() { return QStringLiteral("target-lang-name"); } -static inline QString writeAttribute() { return QStringLiteral("write"); } -static inline QString opaqueContainerFieldAttribute() { return QStringLiteral("opaque-container"); } -static inline QString replaceAttribute() { return QStringLiteral("replace"); } -static inline QString toAttribute() { return QStringLiteral("to"); } -static inline QString signatureAttribute() { return QStringLiteral("signature"); } -static inline QString snippetAttribute() { return QStringLiteral("snippet"); } -static inline QString snakeCaseAttribute() { return QStringLiteral("snake-case"); } -static inline QString staticAttribute() { return QStringLiteral("static"); } -static inline QString classmethodAttribute() { return QStringLiteral("classmethod"); } -static inline QString threadAttribute() { return QStringLiteral("thread"); } -static inline QString sourceAttribute() { return QStringLiteral("source"); } -static inline QString streamAttribute() { return QStringLiteral("stream"); } -static inline QString privateAttribute() { return QStringLiteral("private"); } -static inline QString xPathAttribute() { return QStringLiteral("xpath"); } -static inline QString virtualSlotAttribute() { return QStringLiteral("virtual-slot"); } -static inline QString visibleAttribute() { return QStringLiteral("visible"); } -static inline QString enumIdentifiedByValueAttribute() { return QStringLiteral("identified-by-value"); } - -static inline QString noAttributeValue() { return QStringLiteral("no"); } -static inline QString yesAttributeValue() { return QStringLiteral("yes"); } -static inline QString trueAttributeValue() { return QStringLiteral("true"); } -static inline QString falseAttributeValue() { return QStringLiteral("false"); } +constexpr auto allowThreadAttribute = "allow-thread"_L1; +constexpr auto checkFunctionAttribute = "check-function"_L1; +constexpr auto copyableAttribute = "copyable"_L1; +constexpr auto accessAttribute = "access"_L1; +constexpr auto actionAttribute = "action"_L1; +constexpr auto quoteAfterLineAttribute = "quote-after-line"_L1; +constexpr auto quoteBeforeLineAttribute = "quote-before-line"_L1; +constexpr auto textAttribute = "text"_L1; +constexpr auto nameAttribute = "name"_L1; +constexpr auto sinceAttribute = "since"_L1; +constexpr auto untilAttribute = "until"_L1; +constexpr auto defaultSuperclassAttribute = "default-superclass"_L1; +constexpr auto deleteInMainThreadAttribute = "delete-in-main-thread"_L1; +constexpr auto deprecatedAttribute = "deprecated"_L1; +constexpr auto disableWrapperAttribute = "disable-wrapper"_L1; +constexpr auto docFileAttribute = "doc-file"_L1; +constexpr auto exceptionHandlingAttribute = "exception-handling"_L1; +constexpr auto extensibleAttribute = "extensible"_L1; +constexpr auto fileNameAttribute = "file-name"_L1; +constexpr auto fileAttribute = "file"_L1; +constexpr auto flagsAttribute = "flags"_L1; +constexpr auto forceAbstractAttribute = "force-abstract"_L1; +constexpr auto forceIntegerAttribute = "force-integer"_L1; +constexpr auto formatAttribute = "format"_L1; +constexpr auto generateUsingAttribute = "generate-using"_L1; +constexpr auto generateFunctionsAttribute = "generate-functions"_L1; +constexpr auto classAttribute = "class"_L1; +constexpr auto generateAttribute = "generate"_L1; +constexpr auto generateGetSetDefAttribute = "generate-getsetdef"_L1; +constexpr auto genericClassAttribute = "generic-class"_L1; +constexpr auto indexAttribute = "index"_L1; +constexpr auto invalidateAfterUseAttribute = "invalidate-after-use"_L1; +constexpr auto isNullAttribute = "isNull"_L1; +constexpr auto locationAttribute = "location"_L1; +constexpr auto modifiedTypeAttribute = "modified-type"_L1; +constexpr auto opaqueContainerAttribute = "opaque-containers"_L1; +constexpr auto operatorBoolAttribute = "operator-bool"_L1; +constexpr auto parentManagementAttribute = "parent-management"_L1; +constexpr auto pyiTypeAttribute = "pyi-type"_L1; +constexpr auto overloadNumberAttribute = "overload-number"_L1; +constexpr auto ownershipAttribute = "owner"_L1; +constexpr auto packageAttribute = "package"_L1; +constexpr auto polymorphicBaseAttribute = "polymorphic-base"_L1; +constexpr auto positionAttribute = "position"_L1; +constexpr auto preferredConversionAttribute = "preferred-conversion"_L1; +constexpr auto preferredTargetLangTypeAttribute = "preferred-target-lang-type"_L1; +constexpr auto pythonEnumTypeAttribute = "python-type"_L1; +constexpr auto pythonOverrideAttribute = "python-override"_L1; +constexpr auto cppEnumTypeAttribute = "cpp-type"_L1; +constexpr auto qtMetaObjectFunctionsAttribute = "qt-metaobject"_L1; +constexpr auto qtMetaTypeAttribute = "qt-register-metatype"_L1; +constexpr auto removeAttribute = "remove"_L1; +constexpr auto renameAttribute = "rename"_L1; +constexpr auto readAttribute = "read"_L1; +constexpr auto targetLangNameAttribute = "target-lang-name"_L1; +constexpr auto writeAttribute = "write"_L1; +constexpr auto opaqueContainerFieldAttribute = "opaque-container"_L1; +constexpr auto replaceAttribute = "replace"_L1; +constexpr auto toAttribute = "to"_L1; +constexpr auto signatureAttribute = "signature"_L1; +constexpr auto snippetAttribute = "snippet"_L1; +constexpr auto snakeCaseAttribute = "snake-case"_L1; +constexpr auto staticAttribute = "static"_L1; +constexpr auto classmethodAttribute = "classmethod"_L1; +constexpr auto threadAttribute = "thread"_L1; +constexpr auto sourceAttribute = "source"_L1; +constexpr auto streamAttribute = "stream"_L1; +constexpr auto privateAttribute = "private"_L1; +constexpr auto xPathAttribute = "xpath"_L1; +constexpr auto virtualSlotAttribute = "virtual-slot"_L1; +constexpr auto visibleAttribute = "visible"_L1; +constexpr auto enumIdentifiedByValueAttribute = "identified-by-value"_L1; +constexpr auto subModuleOfAttribute = "submodule-of"_L1; + +constexpr auto noAttributeValue = "no"_L1; +constexpr auto yesAttributeValue = "yes"_L1; +constexpr auto trueAttributeValue = "true"_L1; +constexpr auto falseAttributeValue = "false"_L1; static bool isTypeEntry(StackElement el) { @@ -146,7 +153,7 @@ static bool setRejectionRegularExpression(const QString &patternIn, if (patternIn.startsWith(u'^') && patternIn.endsWith(u'$')) pattern = patternIn; else if (patternIn == u"*") - pattern = QStringLiteral("^.*$"); + pattern = "^.*$"_L1; else pattern = u'^' + QRegularExpression::escape(patternIn) + u'$'; re->setPattern(pattern); @@ -157,15 +164,20 @@ static bool setRejectionRegularExpression(const QString &patternIn, return true; } +static inline bool hasFileSnippetAttributes(const QXmlStreamAttributes *attributes) +{ + return attributes->hasAttribute(fileAttribute); +} + // Extract a snippet from a file within annotation "// @snippet label". std::optional extractSnippet(const QString &code, const QString &snippetLabel) { if (snippetLabel.isEmpty()) return code; - const QString pattern = QStringLiteral(R"(^\s*//\s*@snippet\s+)") + const QString pattern = R"(^\s*//\s*@snippet\s+)"_L1 + QRegularExpression::escape(snippetLabel) - + QStringLiteral(R"(\s*$)"); + + R"(\s*$)"_L1; const QRegularExpression snippetRe(pattern); Q_ASSERT(snippetRe.isValid()); @@ -187,48 +199,31 @@ std::optional return CodeSnipAbstract::fixSpaces(result); } -template +template struct EnumLookup { QStringView name; EnumType value; }; -template -bool operator==(const EnumLookup &e1, const EnumLookup &e2) -{ - return e1.name.compare(e2.name, cs) == 0; -} - -template -bool operator<(const EnumLookup &e1, const EnumLookup &e2) -{ - return e1.name.compare(e2.name, cs) < 0; -} - // Helper macros to define lookup functions that take a QStringView needle // and an optional default return value. #define ENUM_LOOKUP_BEGIN(EnumType, caseSensitivity, functionName) \ static std::optional functionName(QStringView needle) \ { \ - using HaystackEntry = EnumLookup; \ - static const HaystackEntry haystack[] = - -#define ENUM_LOOKUP_LINEAR_SEARCH() \ - const auto end = haystack + sizeof(haystack) / sizeof(haystack[0]); \ - const auto it = std::find(haystack, end, HaystackEntry{needle, {} }); \ + using HaystackEntry = EnumLookup; \ + constexpr auto cs = caseSensitivity; \ + static constexpr HaystackEntry haystack[] = + +#define ENUM_LOOKUP_LINEAR_SEARCH \ + auto pred = [cs, needle](const HaystackEntry &he) { \ + return he.name.compare(needle, cs) == 0; \ + }; \ + auto end = std::cend(haystack); \ + auto it = std::find_if(std::cbegin(haystack), end, pred); \ if (it != end) \ return it->value; \ - return {}; \ -} - -#define ENUM_LOOKUP_BINARY_SEARCH() \ - const auto end = haystack + sizeof(haystack) / sizeof(haystack[0]); \ - const HaystackEntry needleEntry{needle, {} }; \ - const auto lb = std::lower_bound(haystack, end, needleEntry); \ - if (lb != end && *lb == needleEntry) \ - return lb->value; \ - return {}; \ + return std::nullopt; \ } ENUM_LOOKUP_BEGIN(TypeSystem::AllowThread, Qt::CaseInsensitive, @@ -240,7 +235,7 @@ ENUM_LOOKUP_BEGIN(TypeSystem::AllowThread, Qt::CaseInsensitive, {u"no", TypeSystem::AllowThread::Disallow}, {u"false", TypeSystem::AllowThread::Disallow}, }; -ENUM_LOOKUP_LINEAR_SEARCH() +ENUM_LOOKUP_LINEAR_SEARCH ENUM_LOOKUP_BEGIN(TypeSystem::BoolCast, Qt::CaseInsensitive, @@ -251,7 +246,7 @@ ENUM_LOOKUP_BEGIN(TypeSystem::BoolCast, Qt::CaseInsensitive, {u"no", TypeSystem::BoolCast::Disabled}, {u"false", TypeSystem::BoolCast::Disabled}, }; -ENUM_LOOKUP_LINEAR_SEARCH() +ENUM_LOOKUP_LINEAR_SEARCH ENUM_LOOKUP_BEGIN(TypeSystem::PythonEnumType, Qt::CaseSensitive, pythonEnumTypeFromAttribute) @@ -261,7 +256,7 @@ ENUM_LOOKUP_BEGIN(TypeSystem::PythonEnumType, Qt::CaseSensitive, {u"Flag", TypeSystem::PythonEnumType::Flag}, {u"IntFlag", TypeSystem::PythonEnumType::IntFlag}, }; -ENUM_LOOKUP_LINEAR_SEARCH() +ENUM_LOOKUP_LINEAR_SEARCH ENUM_LOOKUP_BEGIN(TypeSystem::QtMetaTypeRegistration, Qt::CaseSensitive, qtMetaTypeFromAttribute) @@ -272,7 +267,7 @@ ENUM_LOOKUP_BEGIN(TypeSystem::QtMetaTypeRegistration, Qt::CaseSensitive, {u"no", TypeSystem::QtMetaTypeRegistration::Disabled}, {u"false", TypeSystem::QtMetaTypeRegistration::Disabled}, }; -ENUM_LOOKUP_LINEAR_SEARCH() +ENUM_LOOKUP_LINEAR_SEARCH ENUM_LOOKUP_BEGIN(TypeSystem::Language, Qt::CaseInsensitive, languageFromAttribute) @@ -282,7 +277,7 @@ ENUM_LOOKUP_BEGIN(TypeSystem::Language, Qt::CaseInsensitive, {u"shell", TypeSystem::ShellCode}, // coloca no header, mas antes da declaracao da classe {u"target", TypeSystem::TargetLangCode} // em algum lugar do cpp }; -ENUM_LOOKUP_BINARY_SEARCH() +ENUM_LOOKUP_LINEAR_SEARCH ENUM_LOOKUP_BEGIN(TypeSystem::Ownership, Qt::CaseInsensitive, ownershipFromFromAttribute) @@ -291,7 +286,7 @@ ENUM_LOOKUP_BEGIN(TypeSystem::Ownership, Qt::CaseInsensitive, {u"c++", TypeSystem::CppOwnership}, {u"default", TypeSystem::DefaultOwnership} }; -ENUM_LOOKUP_LINEAR_SEARCH() +ENUM_LOOKUP_LINEAR_SEARCH ENUM_LOOKUP_BEGIN(AddedFunction::Access, Qt::CaseInsensitive, addedFunctionAccessFromAttribute) @@ -299,7 +294,7 @@ ENUM_LOOKUP_BEGIN(AddedFunction::Access, Qt::CaseInsensitive, {u"public", AddedFunction::Public}, {u"protected", AddedFunction::Protected}, }; -ENUM_LOOKUP_LINEAR_SEARCH() +ENUM_LOOKUP_LINEAR_SEARCH ENUM_LOOKUP_BEGIN(FunctionModification::ModifierFlag, Qt::CaseSensitive, modifierFromAttribute) @@ -307,12 +302,11 @@ ENUM_LOOKUP_BEGIN(FunctionModification::ModifierFlag, Qt::CaseSensitive, {u"private", FunctionModification::Private}, {u"public", FunctionModification::Public}, {u"protected", FunctionModification::Protected}, - {u"friendly", FunctionModification::Friendly}, {u"rename", FunctionModification::Rename}, {u"final", FunctionModification::Final}, {u"non-final", FunctionModification::NonFinal} }; -ENUM_LOOKUP_LINEAR_SEARCH() +ENUM_LOOKUP_LINEAR_SEARCH ENUM_LOOKUP_BEGIN(ReferenceCount::Action, Qt::CaseInsensitive, referenceCountFromAttribute) @@ -323,7 +317,7 @@ ENUM_LOOKUP_BEGIN(ReferenceCount::Action, Qt::CaseInsensitive, {u"set", ReferenceCount::Set}, {u"ignore", ReferenceCount::Ignore} }; -ENUM_LOOKUP_LINEAR_SEARCH() +ENUM_LOOKUP_LINEAR_SEARCH ENUM_LOOKUP_BEGIN(ArgumentOwner::Action, Qt::CaseInsensitive, argumentOwnerActionFromAttribute) @@ -331,16 +325,17 @@ ENUM_LOOKUP_BEGIN(ArgumentOwner::Action, Qt::CaseInsensitive, {u"add", ArgumentOwner::Add}, {u"remove", ArgumentOwner::Remove} }; -ENUM_LOOKUP_LINEAR_SEARCH() +ENUM_LOOKUP_LINEAR_SEARCH ENUM_LOOKUP_BEGIN(TypeSystem::CodeSnipPosition, Qt::CaseInsensitive, codeSnipPositionFromAttribute) { {u"beginning", TypeSystem::CodeSnipPositionBeginning}, {u"end", TypeSystem::CodeSnipPositionEnd}, - {u"declaration", TypeSystem::CodeSnipPositionDeclaration} + {u"declaration", TypeSystem::CodeSnipPositionDeclaration}, + {u"override", TypeSystem::CodeSnipPositionPyOverride} }; -ENUM_LOOKUP_LINEAR_SEARCH() +ENUM_LOOKUP_LINEAR_SEARCH ENUM_LOOKUP_BEGIN(Include::IncludeType, Qt::CaseInsensitive, locationFromAttribute) @@ -349,7 +344,7 @@ ENUM_LOOKUP_BEGIN(Include::IncludeType, Qt::CaseInsensitive, {u"local", Include::LocalPath}, {u"target", Include::TargetLangImport} }; -ENUM_LOOKUP_LINEAR_SEARCH() +ENUM_LOOKUP_LINEAR_SEARCH ENUM_LOOKUP_BEGIN(TypeSystem::DocModificationMode, Qt::CaseInsensitive, docModificationFromAttribute) @@ -358,7 +353,7 @@ ENUM_LOOKUP_BEGIN(TypeSystem::DocModificationMode, Qt::CaseInsensitive, {u"prepend", TypeSystem::DocModificationPrepend}, {u"replace", TypeSystem::DocModificationReplace} }; -ENUM_LOOKUP_LINEAR_SEARCH() +ENUM_LOOKUP_LINEAR_SEARCH ENUM_LOOKUP_BEGIN(ContainerTypeEntry::ContainerKind, Qt::CaseSensitive, containerTypeFromAttribute) @@ -377,7 +372,7 @@ ENUM_LOOKUP_BEGIN(ContainerTypeEntry::ContainerKind, Qt::CaseSensitive, {u"pair", ContainerTypeEntry::PairContainer}, {u"span", ContainerTypeEntry::SpanContainer} }; -ENUM_LOOKUP_LINEAR_SEARCH() +ENUM_LOOKUP_LINEAR_SEARCH ENUM_LOOKUP_BEGIN(TypeRejection::MatchType, Qt::CaseSensitive, typeRejectionFromAttribute) @@ -389,7 +384,7 @@ ENUM_LOOKUP_BEGIN(TypeRejection::MatchType, Qt::CaseSensitive, {u"argument-type", TypeRejection::ArgumentType}, {u"return-type", TypeRejection::ReturnType} }; -ENUM_LOOKUP_LINEAR_SEARCH() +ENUM_LOOKUP_LINEAR_SEARCH ENUM_LOOKUP_BEGIN(TypeSystem::ExceptionHandling, Qt::CaseSensitive, exceptionHandlingFromAttribute) @@ -401,7 +396,7 @@ ENUM_LOOKUP_BEGIN(TypeSystem::ExceptionHandling, Qt::CaseSensitive, {u"yes", TypeSystem::ExceptionHandling::On}, {u"true", TypeSystem::ExceptionHandling::On}, }; -ENUM_LOOKUP_LINEAR_SEARCH() +ENUM_LOOKUP_LINEAR_SEARCH ENUM_LOOKUP_BEGIN(TypeSystem::SmartPointerType, Qt::CaseSensitive, smartPointerTypeFromAttribute) @@ -411,7 +406,7 @@ ENUM_LOOKUP_BEGIN(TypeSystem::SmartPointerType, Qt::CaseSensitive, {u"value-handle", TypeSystem::SmartPointerType::ValueHandle}, {u"shared", TypeSystem::SmartPointerType::Shared} }; -ENUM_LOOKUP_LINEAR_SEARCH() +ENUM_LOOKUP_LINEAR_SEARCH template static std::optional @@ -522,7 +517,7 @@ ENUM_LOOKUP_BEGIN(TypeSystem::SnakeCase, Qt::CaseSensitive, {u"true", TypeSystem::SnakeCase::Enabled}, {u"both", TypeSystem::SnakeCase::Both}, }; -ENUM_LOOKUP_LINEAR_SEARCH() +ENUM_LOOKUP_LINEAR_SEARCH ENUM_LOOKUP_BEGIN(TypeSystem::Visibility, Qt::CaseSensitive, visibilityFromAttribute) @@ -533,10 +528,10 @@ ENUM_LOOKUP_BEGIN(TypeSystem::Visibility, Qt::CaseSensitive, {u"yes", TypeSystem::Visibility::Visible}, {u"true", TypeSystem::Visibility::Visible}, }; -ENUM_LOOKUP_LINEAR_SEARCH() +ENUM_LOOKUP_LINEAR_SEARCH static int indexOfAttribute(const QXmlStreamAttributes &atts, - QStringView name) + QAnyStringView name) { for (qsizetype i = 0, size = atts.size(); i < size; ++i) { if (atts.at(i).qualifiedName() == name) @@ -692,7 +687,7 @@ static QString msgReaderError(const ConditionalStreamReader &reader, const QStri } static QString msgUnimplementedElementWarning(const ConditionalStreamReader &reader, - QStringView name) + QAnyStringView name) { QString message; QTextStream(&message) << "The element \"" << name @@ -717,7 +712,7 @@ static inline QString msgUnimplementedAttributeWarning(const ConditionalStreamRe static QString msgUnimplementedAttributeValueWarning(const ConditionalStreamReader &reader, - QStringView name, QStringView value) + QAnyStringView name, QAnyStringView value) { QString message; QTextStream(&message) << "The value \"" << value @@ -737,9 +732,9 @@ static inline static bool addRejection(TypeDatabase *database, bool generate, QXmlStreamAttributes *attributes, QString *errorMessage) { - const auto classIndex = indexOfAttribute(*attributes, classAttribute()); + const auto classIndex = indexOfAttribute(*attributes, classAttribute); if (classIndex == -1) { - *errorMessage = msgMissingAttribute(classAttribute()); + *errorMessage = msgMissingAttribute(classAttribute); return false; } @@ -1125,7 +1120,7 @@ bool TypeSystemParser::characters(const String &ch) bool TypeSystemParser::importFileElement(const QXmlStreamAttributes &atts) { - const QString fileName = atts.value(nameAttribute()).toString(); + const QString fileName = atts.value(nameAttribute).toString(); if (fileName.isEmpty()) { m_error = u"Required attribute 'name' missing for include-file tag."_s; return false; @@ -1140,11 +1135,11 @@ bool TypeSystemParser::importFileElement(const QXmlStreamAttributes &atts) } } - const auto quoteFrom = atts.value(quoteAfterLineAttribute()); + const auto quoteFrom = atts.value(quoteAfterLineAttribute); bool foundFromOk = quoteFrom.isEmpty(); bool from = quoteFrom.isEmpty(); - const auto quoteTo = atts.value(quoteBeforeLineAttribute()); + const auto quoteTo = atts.value(quoteBeforeLineAttribute); bool foundToOk = quoteTo.isEmpty(); bool to = true; @@ -1164,10 +1159,10 @@ bool TypeSystemParser::importFileElement(const QXmlStreamAttributes &atts) } } if (!foundFromOk || !foundToOk) { - QString fromError = QStringLiteral("Could not find quote-after-line='%1' in file '%2'.") - .arg(quoteFrom.toString(), fileName); - QString toError = QStringLiteral("Could not find quote-before-line='%1' in file '%2'.") - .arg(quoteTo.toString(), fileName); + QString fromError = QString::fromLatin1("Could not find quote-after-line='%1' in file '%2'.") + .arg(quoteFrom.toString(), fileName); + QString toError = QString::fromLatin1("Could not find quote-before-line='%1' in file '%2'.") + .arg(quoteTo.toString(), fileName); if (!foundToOk) m_error = toError; @@ -1181,29 +1176,27 @@ bool TypeSystemParser::importFileElement(const QXmlStreamAttributes &atts) return true; } -static bool convertBoolean(QStringView value, const QString &attributeName, bool defaultValue) +static bool convertBoolean(QStringView value, QAnyStringView attributeName, bool defaultValue) { - if (value.compare(trueAttributeValue(), Qt::CaseInsensitive) == 0 - || value.compare(yesAttributeValue(), Qt::CaseInsensitive) == 0) { + if (value.compare(trueAttributeValue, Qt::CaseInsensitive) == 0 + || value.compare(yesAttributeValue, Qt::CaseInsensitive) == 0) { return true; } - if (value.compare(falseAttributeValue(), Qt::CaseInsensitive) == 0 - || value.compare(noAttributeValue(), Qt::CaseInsensitive) == 0) { + if (value.compare(falseAttributeValue, Qt::CaseInsensitive) == 0 + || value.compare(noAttributeValue, Qt::CaseInsensitive) == 0) { return false; } - const QString warn = QStringLiteral("Boolean value '%1' not supported in attribute '%2'. Use 'yes' or 'no'. Defaulting to '%3'.") - .arg(value) - .arg(attributeName, - defaultValue ? yesAttributeValue() : noAttributeValue()); - - qCWarning(lcShiboken).noquote().nospace() << warn; + qCWarning(lcShiboken).noquote().nospace() << "Boolean value '" << value + << "' not supported in attribute '" << attributeName + << "'. Use 'yes' or 'no'. Defaulting to '" + << (defaultValue ? yesAttributeValue : noAttributeValue) << "'."; return defaultValue; } static bool convertRemovalAttribute(QStringView value) { return value == u"all" // Legacy - || convertBoolean(value, removeAttribute(), false); + || convertBoolean(value, removeAttribute, false); } // Check whether an entry should be dropped, allowing for dropping the module @@ -1229,7 +1222,7 @@ static bool shouldDropTypeEntry(const TypeDatabase *db, static QString checkSignatureError(const QString& signature, const QString& tag) { QString funcName = signature.left(signature.indexOf(u'(')).trimmed(); - static const QRegularExpression whiteSpace(QStringLiteral("\\s")); + static const QRegularExpression whiteSpace("\\s"_L1); Q_ASSERT(whiteSpace.isValid()); if (!funcName.startsWith(u"operator ") && funcName.contains(whiteSpace)) { return QString::fromLatin1("Error in <%1> tag signature attribute '%2'.\n" @@ -1308,7 +1301,7 @@ CustomTypeEntryPtr TypeSystemParser::parseCustomTypeEntry(const ConditionalStrea auto result = std::make_shared(name, since, m_contextStack.top()->entry); for (auto i = attributes->size() - 1; i >= 0; --i) { const auto name = attributes->at(i).qualifiedName(); - if (name == checkFunctionAttribute()) + if (name == checkFunctionAttribute) result->setCheckFunction(attributes->takeAt(i).value().toString()); } return result; @@ -1344,9 +1337,9 @@ FlagsTypeEntryPtr const QString targetLangFlagName = lst.join(u'.'); const QString &targetLangQualifier = enumEntry->targetLangQualifier(); if (targetLangFlagName != targetLangQualifier) { - qCWarning(lcShiboken).noquote().nospace() - << QStringLiteral("enum %1 and flags %2 (%3) differ in qualifiers") - .arg(targetLangQualifier, lst.value(0), targetLangFlagName); + qCWarning(lcShiboken, "enum %s and flags %s (%s) differ in qualifiers", + qPrintable(targetLangQualifier), qPrintable(lst.value(0)), + qPrintable(targetLangFlagName)); } ftype->setFlagsName(name); @@ -1452,16 +1445,16 @@ PrimitiveTypeEntryPtr return nullptr; for (auto i = attributes->size() - 1; i >= 0; --i) { const auto name = attributes->at(i).qualifiedName(); - if (name == targetLangNameAttribute()) { + if (name == targetLangNameAttribute) { type->setTargetLangName(attributes->takeAt(i).value().toString()); } else if (name == u"target-lang-api-name") { targetLangApiName = attributes->takeAt(i).value().toString(); - } else if (name == preferredConversionAttribute()) { + } else if (name == preferredConversionAttribute) { qCWarning(lcShiboken, "%s", qPrintable(msgUnimplementedAttributeWarning(reader, name))); - } else if (name == preferredTargetLangTypeAttribute()) { + } else if (name == preferredTargetLangTypeAttribute) { const bool v = convertBoolean(attributes->takeAt(i).value(), - preferredTargetLangTypeAttribute(), true); + preferredTargetLangTypeAttribute, true); type->setPreferredTargetLangType(v); } else if (name == u"default-constructor") { type->setDefaultConstructor(attributes->takeAt(i).value().toString()); @@ -1535,7 +1528,7 @@ ContainerTypeEntryPtr for (auto i = attributes->size() - 1; i >= 0; --i) { const auto name = attributes->at(i).qualifiedName(); - if (name == opaqueContainerAttribute()) { + if (name == opaqueContainerAttribute) { const auto attribute = attributes->takeAt(i); OpaqueContainers oc; if (!parseOpaqueContainers(attribute.value(), &oc)) @@ -1553,16 +1546,16 @@ bool TypeSystemParser::parseOpaqueContainerElement(QXmlStreamAttributes *attribu OpaqueContainers oc; for (auto i = attributes->size() - 1; i >= 0; --i) { const auto name = attributes->at(i).qualifiedName(); - if (name == nameAttribute()) { + if (name == nameAttribute) { containerName = attributes->takeAt(i).value().toString(); - } else if (name == opaqueContainerAttribute()) { + } else if (name == opaqueContainerAttribute) { const auto attribute = attributes->takeAt(i); if (!parseOpaqueContainers(attribute.value(), &oc)) return false; } } if (containerName.isEmpty()) { - m_error = msgMissingAttribute(nameAttribute()); + m_error = msgMissingAttribute(nameAttribute); return false; } m_context->opaqueContainerHash[containerName].append(oc); @@ -1589,10 +1582,12 @@ EnumTypeEntryPtr } else if (name == u"lower-bound") { qCWarning(lcShiboken, "%s", qPrintable(msgUnimplementedAttributeWarning(reader, name))); - } else if (name == forceIntegerAttribute()) { + } else if (name == docFileAttribute) { + entry->setDocFile(attributes->takeAt(i).value().toString()); + } else if (name == forceIntegerAttribute) { qCWarning(lcShiboken, "%s", qPrintable(msgUnimplementedAttributeWarning(reader, name))); - } else if (name == pythonEnumTypeAttribute()) { + } else if (name == pythonEnumTypeAttribute) { const auto attribute = attributes->takeAt(i); const auto typeOpt = pythonEnumTypeFromAttribute(attribute.value()); if (typeOpt.has_value()) { @@ -1601,12 +1596,12 @@ EnumTypeEntryPtr qCWarning(lcShiboken, "%s", qPrintable(msgInvalidAttributeValue(attribute))); } - } else if (name == cppEnumTypeAttribute()) { + } else if (name == cppEnumTypeAttribute) { entry->setCppType(attributes->takeAt(i).value().toString()); - } else if (name == extensibleAttribute()) { + } else if (name == extensibleAttribute) { qCWarning(lcShiboken, "%s", qPrintable(msgUnimplementedAttributeWarning(reader, name))); - } else if (name == flagsAttribute()) { + } else if (name == flagsAttribute) { flagNames = attributes->takeAt(i).value().toString(); } } @@ -1654,7 +1649,7 @@ NamespaceTypeEntryPtr } result->setExtends(*extendsIt); attributes->removeAt(i); - } else if (attributeName == visibleAttribute()) { + } else if (attributeName == visibleAttribute) { const auto attribute = attributes->takeAt(i); const auto visibilityOpt = visibilityFromAttribute(attribute.value()); if (!visibilityOpt.has_value()) { @@ -1662,11 +1657,12 @@ NamespaceTypeEntryPtr return nullptr; } visibility = visibilityOpt.value(); - } else if (attributeName == generateAttribute()) { - if (!convertBoolean(attributes->takeAt(i).value(), generateAttribute(), true)) + } else if (attributeName == generateAttribute) { + if (!convertBoolean(attributes->takeAt(i).value(), generateAttribute, true)) visibility = TypeSystem::Visibility::Invisible; - } else if (attributeName == generateUsingAttribute()) { - result->setGenerateUsing(convertBoolean(attributes->takeAt(i).value(), generateUsingAttribute(), true)); + } else if (attributeName == generateUsingAttribute) { + result->setGenerateUsing(convertBoolean(attributes->takeAt(i).value(), + generateUsingAttribute, true)); } } @@ -1716,15 +1712,18 @@ FunctionTypeEntryPtr const bool hasModification = attributes->size() < oldAttributesSize; QString originalSignature; + QString docFile; for (auto i = attributes->size() - 1; i >= 0; --i) { const auto name = attributes->at(i).qualifiedName(); - if (name == signatureAttribute()) + if (name == signatureAttribute) originalSignature = attributes->takeAt(i).value().toString().simplified(); + else if (name == docFileAttribute) + docFile = attributes->takeAt(i).value().toString(); } const QString signature = TypeDatabase::normalizedSignature(originalSignature); if (signature.isEmpty()) { - m_error = msgMissingAttribute(signatureAttribute()); + m_error = msgMissingAttribute(signatureAttribute); return nullptr; } @@ -1739,13 +1738,14 @@ FunctionTypeEntryPtr if (!existingType) { auto result = std::make_shared(name, signature, since, currentParentTypeEntry()); + result->setTargetLangPackage(m_defaultPackage); + result->setDocFile(docFile); applyCommonAttributes(reader, result, attributes); return result; } if (existingType->type() != TypeEntry::FunctionType) { - m_error = QStringLiteral("%1 expected to be a function, but isn't! Maybe it was already declared as a class or something else.") - .arg(name); + m_error = name + " expected to be a function, but isn't! Maybe it was already declared as a class or something else."_L1; return nullptr; } @@ -1767,9 +1767,9 @@ TypedefEntryPtr m_error = u"typedef entries must be nested in namespaces or type system."_s; return nullptr; } - const auto sourceIndex = indexOfAttribute(*attributes, sourceAttribute()); + const auto sourceIndex = indexOfAttribute(*attributes, sourceAttribute); if (sourceIndex == -1) { - m_error = msgMissingAttribute(sourceAttribute()); + m_error = msgMissingAttribute(sourceAttribute); return nullptr; } const QString sourceType = attributes->takeAt(sourceIndex).value().toString(); @@ -1793,34 +1793,38 @@ void TypeSystemParser::applyComplexTypeAttributes(const ConditionalStreamReader QString package = m_defaultPackage; for (auto i = attributes->size() - 1; i >= 0; --i) { const auto name = attributes->at(i).qualifiedName(); - if (name == streamAttribute()) { - ctype->setStream(convertBoolean(attributes->takeAt(i).value(), streamAttribute(), false)); - } else if (name == privateAttribute()) { + if (name == streamAttribute) { + ctype->setStream(convertBoolean(attributes->takeAt(i).value(), streamAttribute, false)); + } else if (name == privateAttribute) { ctype->setPrivate(convertBoolean(attributes->takeAt(i).value(), - privateAttribute(), false)); - } else if (name == generateAttribute()) { - generate = convertBoolean(attributes->takeAt(i).value(), generateAttribute(), true); - } else if (name ==packageAttribute()) { + privateAttribute, false)); + } else if (name == generateAttribute) { + generate = convertBoolean(attributes->takeAt(i).value(), generateAttribute, true); + } else if (name ==packageAttribute) { package = attributes->takeAt(i).value().toString(); - } else if (name == defaultSuperclassAttribute()) { + } else if (name == defaultSuperclassAttribute) { ctype->setDefaultSuperclass(attributes->takeAt(i).value().toString()); - } else if (name == genericClassAttribute()) { + } else if (name == genericClassAttribute) { qCWarning(lcShiboken, "%s", qPrintable(msgUnimplementedAttributeWarning(reader, name))); - const bool v = convertBoolean(attributes->takeAt(i).value(), genericClassAttribute(), false); + const bool v = convertBoolean(attributes->takeAt(i).value(), + genericClassAttribute, false); ctype->setGenericClass(v); - } else if (name == targetLangNameAttribute()) { + } else if (name == targetLangNameAttribute) { ctype->setTargetLangName(attributes->takeAt(i).value().toString()); - } else if (name == u"polymorphic-base") { - ctype->setPolymorphicIdValue(attributes->takeAt(i).value().toString()); + } else if (name == polymorphicBaseAttribute) { + const bool v = convertBoolean(attributes->takeAt(i).value(), + polymorphicBaseAttribute, false); + ctype->setIsPolymorphicBase(v); } else if (name == u"polymorphic-name-function") { ctype->setPolymorphicNameFunction(attributes->takeAt(i).value().toString()); } else if (name == u"polymorphic-id-expression") { ctype->setPolymorphicIdValue(attributes->takeAt(i).value().toString()); - } else if (name == copyableAttribute()) { - const bool v = convertBoolean(attributes->takeAt(i).value(), copyableAttribute(), false); + } else if (name == copyableAttribute) { + const bool v = convertBoolean(attributes->takeAt(i).value(), + copyableAttribute, false); ctype->setCopyable(v ? ComplexTypeEntry::CopyableSet : ComplexTypeEntry::NonCopyableSet); - } else if (name == exceptionHandlingAttribute()) { + } else if (name == exceptionHandlingAttribute) { const auto attribute = attributes->takeAt(i); const auto exceptionOpt = exceptionHandlingFromAttribute(attribute.value()); if (exceptionOpt.has_value()) { @@ -1829,7 +1833,7 @@ void TypeSystemParser::applyComplexTypeAttributes(const ConditionalStreamReader qCWarning(lcShiboken, "%s", qPrintable(msgInvalidAttributeValue(attribute))); } - } else if (name == allowThreadAttribute()) { + } else if (name == allowThreadAttribute) { const auto attribute = attributes->takeAt(i); const auto allowThreadOpt = allowThreadFromAttribute(attribute.value()); if (allowThreadOpt.has_value()) { @@ -1843,19 +1847,25 @@ void TypeSystemParser::applyComplexTypeAttributes(const ConditionalStreamReader qPrintable(msgUnimplementedAttributeWarning(reader, name))); } else if (name == u"hash-function") { ctype->setHashFunction(attributes->takeAt(i).value().toString()); - } else if (name == forceAbstractAttribute()) { - if (convertBoolean(attributes->takeAt(i).value(), forceAbstractAttribute(), false)) + } else if (name == forceAbstractAttribute) { + if (convertBoolean(attributes->takeAt(i).value(), forceAbstractAttribute, false)) ctype->setTypeFlags(ctype->typeFlags() | ComplexTypeEntry::ForceAbstract); - } else if (name == deprecatedAttribute()) { - if (convertBoolean(attributes->takeAt(i).value(), deprecatedAttribute(), false)) + } else if (name == deprecatedAttribute) { + if (convertBoolean(attributes->takeAt(i).value(), deprecatedAttribute, false)) ctype->setTypeFlags(ctype->typeFlags() | ComplexTypeEntry::Deprecated); - } else if (name == disableWrapperAttribute()) { - if (convertBoolean(attributes->takeAt(i).value(), disableWrapperAttribute(), false)) + } else if (name == disableWrapperAttribute) { + if (convertBoolean(attributes->takeAt(i).value(), disableWrapperAttribute, false)) ctype->setTypeFlags(ctype->typeFlags() | ComplexTypeEntry::DisableWrapper); - } else if (name == deleteInMainThreadAttribute()) { - if (convertBoolean(attributes->takeAt(i).value(), deleteInMainThreadAttribute(), false)) + } else if (name == deleteInMainThreadAttribute) { + if (convertBoolean(attributes->takeAt(i).value(), deleteInMainThreadAttribute, false)) ctype->setDeleteInMainThread(true); - } else if (name == generateFunctionsAttribute()) { + } else if (name == qtMetaObjectFunctionsAttribute) { + if (!convertBoolean(attributes->takeAt(i).value(), + qtMetaObjectFunctionsAttribute, true)) { + ctype->setTypeFlags(ctype->typeFlags() + | ComplexTypeEntry::DisableQtMetaObjectFunctions); + } + } else if (name == generateFunctionsAttribute) { const auto names = attributes->takeAt(i).value(); const auto nameList = names.split(u';', Qt::SkipEmptyParts); QSet nameSet; @@ -1864,7 +1874,7 @@ void TypeSystemParser::applyComplexTypeAttributes(const ConditionalStreamReader ctype->setGenerateFunctions(nameSet); } else if (name == u"target-type") { ctype->setTargetType(attributes->takeAt(i).value().toString()); - } else if (name == snakeCaseAttribute()) { + } else if (name == snakeCaseAttribute) { const auto attribute = attributes->takeAt(i); const auto snakeCaseOpt = snakeCaseFromAttribute(attribute.value()); if (snakeCaseOpt.has_value()) { @@ -1873,7 +1883,7 @@ void TypeSystemParser::applyComplexTypeAttributes(const ConditionalStreamReader qCWarning(lcShiboken, "%s", qPrintable(msgInvalidAttributeValue(attribute))); } - } else if (name == isNullAttribute()) { + } else if (name == isNullAttribute) { const auto attribute = attributes->takeAt(i); const auto boolCastOpt = boolCastFromAttribute(attribute.value()); if (boolCastOpt.has_value()) { @@ -1882,7 +1892,7 @@ void TypeSystemParser::applyComplexTypeAttributes(const ConditionalStreamReader qCWarning(lcShiboken, "%s", qPrintable(msgInvalidAttributeValue(attribute))); } - } else if (name == operatorBoolAttribute()) { + } else if (name == operatorBoolAttribute) { const auto attribute = attributes->takeAt(i); const auto boolCastOpt = boolCastFromAttribute(attribute.value()); if (boolCastOpt.has_value()) { @@ -1891,7 +1901,7 @@ void TypeSystemParser::applyComplexTypeAttributes(const ConditionalStreamReader qCWarning(lcShiboken, "%s", qPrintable(msgInvalidAttributeValue(attribute))); } - } else if (name == qtMetaTypeAttribute()) { + } else if (name == qtMetaTypeAttribute) { const auto attribute = attributes->takeAt(i); const auto qtMetaTypeOpt = qtMetaTypeFromAttribute(attribute.value()); if (qtMetaTypeOpt.has_value()) { @@ -1900,9 +1910,9 @@ void TypeSystemParser::applyComplexTypeAttributes(const ConditionalStreamReader qCWarning(lcShiboken, "%s", qPrintable(msgInvalidAttributeValue(attribute))); } - } else if (name == parentManagementAttribute()) { + } else if (name == parentManagementAttribute) { const auto attribute = attributes->takeAt(i); - if (convertBoolean(attribute.value(), parentManagementAttribute(), false)) + if (convertBoolean(attribute.value(), parentManagementAttribute, false)) ctype->setTypeFlags(ctype->typeFlags() | ComplexTypeEntry::ParentManagement); ComplexTypeEntry::setParentManagementEnabled(true); } @@ -1956,16 +1966,16 @@ bool TypeSystemParser::parseRenameFunction(const ConditionalStreamReader &, QString rename; for (auto i = attributes->size() - 1; i >= 0; --i) { const auto name = attributes->at(i).qualifiedName(); - if (name == signatureAttribute()) { + if (name == signatureAttribute) { // Do not remove as it is needed for the type entry later on signature = attributes->at(i).value().toString().simplified(); - } else if (name == renameAttribute()) { + } else if (name == renameAttribute) { rename = attributes->takeAt(i).value().toString(); } } if (signature.isEmpty()) { - m_error = msgMissingAttribute(signatureAttribute()); + m_error = msgMissingAttribute(signatureAttribute); return false; } @@ -2021,7 +2031,7 @@ bool TypeSystemParser::parseInjectDocumentation(const ConditionalStreamReader &, return false; } mode = modeOpt.value(); - } else if (name == formatAttribute()) { + } else if (name == formatAttribute) { const auto attribute = attributes->takeAt(i); const auto langOpt = languageFromAttribute(attribute.value()); if (!langOpt.has_value()) { @@ -2035,6 +2045,12 @@ bool TypeSystemParser::parseInjectDocumentation(const ConditionalStreamReader &, QString signature = isTypeEntry(topElement) ? QString() : m_currentSignature; DocModification mod(mode, signature); mod.setFormat(lang); + if (hasFileSnippetAttributes(attributes)) { + const auto snippetOptional = readFileSnippet(attributes); + if (!snippetOptional.has_value()) + return false; + mod.setCode(snippetOptional.value().content); + } auto &top = m_contextStack.top(); if (isAddFunction) top->addedFunctions.last()->addDocModification(mod); @@ -2056,9 +2072,9 @@ bool TypeSystemParser::parseModifyDocumentation(const ConditionalStreamReader &, return false; } - const auto xpathIndex = indexOfAttribute(*attributes, xPathAttribute()); + const auto xpathIndex = indexOfAttribute(*attributes, xPathAttribute); if (xpathIndex == -1) { - m_error = msgMissingAttribute(xPathAttribute()); + m_error = msgMissingAttribute(xPathAttribute); return false; } @@ -2075,14 +2091,17 @@ TypeSystemTypeEntryPtr TypeSystemParser::parseRootElement(const ConditionalStrea QXmlStreamAttributes *attributes) { TypeSystem::SnakeCase snakeCase = TypeSystem::SnakeCase::Unspecified; + QString subModuleOf; + QString namespaceBegin; + QString namespaceEnd; for (auto i = attributes->size() - 1; i >= 0; --i) { const auto name = attributes->at(i).qualifiedName(); - if (name == packageAttribute()) { + if (name == packageAttribute) { m_defaultPackage = attributes->takeAt(i).value().toString(); - } else if (name == defaultSuperclassAttribute()) { + } else if (name == defaultSuperclassAttribute) { m_defaultSuperclass = attributes->takeAt(i).value().toString(); - } else if (name == exceptionHandlingAttribute()) { + } else if (name == exceptionHandlingAttribute) { const auto attribute = attributes->takeAt(i); const auto exceptionOpt = exceptionHandlingFromAttribute(attribute.value()); if (exceptionOpt.has_value()) { @@ -2091,7 +2110,7 @@ TypeSystemTypeEntryPtr TypeSystemParser::parseRootElement(const ConditionalStrea qCWarning(lcShiboken, "%s", qPrintable(msgInvalidAttributeValue(attribute))); } - } else if (name == allowThreadAttribute()) { + } else if (name == allowThreadAttribute) { const auto attribute = attributes->takeAt(i); const auto allowThreadOpt = allowThreadFromAttribute(attribute.value()); if (allowThreadOpt.has_value()) { @@ -2100,7 +2119,7 @@ TypeSystemTypeEntryPtr TypeSystemParser::parseRootElement(const ConditionalStrea qCWarning(lcShiboken, "%s", qPrintable(msgInvalidAttributeValue(attribute))); } - } else if (name == snakeCaseAttribute()) { + } else if (name == snakeCaseAttribute) { const auto attribute = attributes->takeAt(i); const auto snakeCaseOpt = snakeCaseFromAttribute(attribute.value()); if (snakeCaseOpt.has_value()) { @@ -2109,6 +2128,12 @@ TypeSystemTypeEntryPtr TypeSystemParser::parseRootElement(const ConditionalStrea qCWarning(lcShiboken, "%s", qPrintable(msgInvalidAttributeValue(attribute))); } + } else if (name == subModuleOfAttribute) { + subModuleOf = attributes->takeAt(i).value().toString(); + } else if (name == "namespace-begin"_L1) { + namespaceBegin = attributes->takeAt(i).value().toString(); + } else if (name == "namespace-end"_L1) { + namespaceEnd = attributes->takeAt(i).value().toString(); } } @@ -2125,9 +2150,14 @@ TypeSystemTypeEntryPtr TypeSystemParser::parseRootElement(const ConditionalStrea if (add) { moduleEntry.reset(new TypeSystemTypeEntry(m_defaultPackage, since, currentParentTypeEntry())); + moduleEntry->setSubModule(subModuleOf); } moduleEntry->setCodeGeneration(m_generate); moduleEntry->setSnakeCase(snakeCase); + if (!namespaceBegin.isEmpty()) + moduleEntry->setNamespaceBegin(namespaceBegin); + if (!namespaceEnd.isEmpty()) + moduleEntry->setNamespaceEnd(namespaceEnd); if ((m_generate == TypeEntry::GenerateForSubclass || m_generate == TypeEntry::GenerateNothing) && !m_defaultPackage.isEmpty()) @@ -2145,10 +2175,10 @@ bool TypeSystemParser::loadTypesystem(const ConditionalStreamReader &, bool generateChild = true; for (auto i = attributes->size() - 1; i >= 0; --i) { const auto name = attributes->at(i).qualifiedName(); - if (name == nameAttribute()) + if (name == nameAttribute) typeSystemName = attributes->takeAt(i).value().toString(); - else if (name == generateAttribute()) - generateChild = convertBoolean(attributes->takeAt(i).value(), generateAttribute(), true); + else if (name == generateAttribute) + generateChild = convertBoolean(attributes->takeAt(i).value(), generateAttribute, true); } if (typeSystemName.isEmpty()) { m_error = u"No typesystem name specified"_s; @@ -2169,9 +2199,9 @@ bool TypeSystemParser::parseRejectEnumValue(const ConditionalStreamReader &, m_error = u" node must be used inside a node"_s; return false; } - const auto nameIndex = indexOfAttribute(*attributes, nameAttribute()); + const auto nameIndex = indexOfAttribute(*attributes, nameAttribute); if (nameIndex == -1) { - m_error = msgMissingAttribute(nameAttribute()); + m_error = msgMissingAttribute(nameAttribute); return false; } m_currentEnum->addEnumValueRejection(attributes->takeAt(nameIndex).value().toString()); @@ -2186,7 +2216,7 @@ bool TypeSystemParser::parseReplaceArgumentType(const ConditionalStreamReader &, m_error = u"Type replacement can only be specified for argument modifications"_s; return false; } - const auto modifiedTypeIndex = indexOfAttribute(*attributes, modifiedTypeAttribute()); + const auto modifiedTypeIndex = indexOfAttribute(*attributes, modifiedTypeAttribute); if (modifiedTypeIndex == -1) { m_error = u"Type replacement requires 'modified-type' attribute"_s; return false; @@ -2214,7 +2244,7 @@ bool TypeSystemParser::parseCustomConversion(const ConditionalStreamReader &, TypeSystem::Language lang = TypeSystem::NativeCode; for (auto i = attributes->size() - 1; i >= 0; --i) { const auto name = attributes->at(i).qualifiedName(); - if (name == classAttribute()) { + if (name == classAttribute) { const auto languageAttribute = attributes->takeAt(i); const auto langOpt = languageFromAttribute(languageAttribute.value()); if (!langOpt.has_value()) { @@ -2224,7 +2254,7 @@ bool TypeSystemParser::parseCustomConversion(const ConditionalStreamReader &, lang = langOpt.value(); } else if (name == u"file") { sourceFile = attributes->takeAt(i).value().toString(); - } else if (name == snippetAttribute()) { + } else if (name == snippetAttribute) { snippetLabel = attributes->takeAt(i).value().toString(); } } @@ -2291,7 +2321,7 @@ bool TypeSystemParser::parseNativeToTarget(const ConditionalStreamReader &, return false; } CodeSnip snip; - if (!readFileSnippet(attributes, &snip)) + if (!readCodeSnippet(attributes, &snip)) return false; m_contextStack.top()->conversionCodeSnips.append(snip); return true; @@ -2308,7 +2338,7 @@ bool TypeSystemParser::parseAddConversion(const ConditionalStreamReader &, QString sourceTypeName; QString typeCheck; CodeSnip snip; - if (!readFileSnippet(attributes, &snip)) + if (!readCodeSnippet(attributes, &snip)) return false; const auto &top = m_contextStack.top(); @@ -2343,7 +2373,7 @@ static bool parseIndex(const QString &index, int *result, QString *errorMessage) bool ok = false; *result = index.toInt(&ok); if (!ok) - *errorMessage = QStringLiteral("Cannot convert '%1' to integer").arg(index); + *errorMessage = QString::fromLatin1("Cannot convert '%1' to integer").arg(index); return ok; } @@ -2378,20 +2408,20 @@ bool TypeSystemParser::parseModifyArgument(const ConditionalStreamReader &, bool resetAfterUse = false; for (auto i = attributes->size() - 1; i >= 0; --i) { const auto name = attributes->at(i).qualifiedName(); - if (name == indexAttribute()) { + if (name == indexAttribute) { index = attributes->takeAt(i).value().toString(); - } else if (name == invalidateAfterUseAttribute()) { + } else if (name == invalidateAfterUseAttribute) { resetAfterUse = convertBoolean(attributes->takeAt(i).value(), - invalidateAfterUseAttribute(), false); - } else if (name == renameAttribute()) { + invalidateAfterUseAttribute, false); + } else if (name == renameAttribute) { renameTo = attributes->takeAt(i).value().toString(); - } else if (name == pyiTypeAttribute()) { + } else if (name == pyiTypeAttribute) { pyiType = attributes->takeAt(i).value().toString(); } } if (index.isEmpty()) { - m_error = msgMissingAttribute(indexAttribute()); + m_error = msgMissingAttribute(indexAttribute); return false; } @@ -2441,7 +2471,7 @@ bool TypeSystemParser::parseDefineOwnership(const ConditionalStreamReader &, std::optional ownershipOpt; for (auto i = attributes->size() - 1; i >= 0; --i) { const auto name = attributes->at(i).qualifiedName(); - if (name == classAttribute()) { + if (name == classAttribute) { const auto classAttribute = attributes->takeAt(i); const auto langOpt = languageFromAttribute(classAttribute.value()); if (!langOpt.has_value() || langOpt.value() == TypeSystem::ShellCode) { @@ -2449,7 +2479,7 @@ bool TypeSystemParser::parseDefineOwnership(const ConditionalStreamReader &, return false; } lang = langOpt.value(); - } else if (name == ownershipAttribute()) { + } else if (name == ownershipAttribute) { const auto attribute = attributes->takeAt(i); ownershipOpt = ownershipFromFromAttribute(attribute.value()); if (!ownershipOpt.has_value()) { @@ -2460,7 +2490,7 @@ bool TypeSystemParser::parseDefineOwnership(const ConditionalStreamReader &, } if (!ownershipOpt.has_value()) { - m_error = QStringLiteral("unspecified ownership"); + m_error = "unspecified ownership"_L1; return false; } auto &lastArgMod = m_contextStack.top()->functionMods.last().argument_mods().last(); @@ -2487,9 +2517,9 @@ bool TypeSystemParser::parseRename(const ConditionalStreamReader &, return false; } - const auto toIndex = indexOfAttribute(*attributes, toAttribute()); + const auto toIndex = indexOfAttribute(*attributes, toAttribute); if (toIndex == -1) { - m_error = msgMissingAttribute(toAttribute()); + m_error = msgMissingAttribute(toAttribute); return false; } const QString renamed_to = attributes->takeAt(toIndex).value().toString(); @@ -2503,20 +2533,20 @@ bool TypeSystemParser::parseModifyField(const ConditionalStreamReader &, FieldModification fm; for (auto i = attributes->size() - 1; i >= 0; --i) { const auto name = attributes->at(i).qualifiedName(); - if (name == nameAttribute()) { + if (name == nameAttribute) { fm.setName(attributes->takeAt(i).value().toString()); - } else if (name == removeAttribute()) { + } else if (name == removeAttribute) { fm.setRemoved(convertRemovalAttribute(attributes->takeAt(i).value())); - } else if (name == opaqueContainerFieldAttribute()) { + } else if (name == opaqueContainerFieldAttribute) { fm.setOpaqueContainer(convertBoolean(attributes->takeAt(i).value(), - opaqueContainerFieldAttribute(), false)); - } else if (name == readAttribute()) { - fm.setReadable(convertBoolean(attributes->takeAt(i).value(), readAttribute(), true)); - } else if (name == writeAttribute()) { - fm.setWritable(convertBoolean(attributes->takeAt(i).value(), writeAttribute(), true)); - } else if (name == renameAttribute()) { + opaqueContainerFieldAttribute, false)); + } else if (name == readAttribute) { + fm.setReadable(convertBoolean(attributes->takeAt(i).value(), readAttribute, true)); + } else if (name == writeAttribute) { + fm.setWritable(convertBoolean(attributes->takeAt(i).value(), writeAttribute, true)); + } else if (name == renameAttribute) { fm.setRenamedToName(attributes->takeAt(i).value().toString()); - } else if (name == snakeCaseAttribute()) { + } else if (name == snakeCaseAttribute) { const auto attribute = attributes->takeAt(i); const auto snakeCaseOpt = snakeCaseFromAttribute(attribute.value()); if (snakeCaseOpt.has_value()) { @@ -2528,7 +2558,7 @@ bool TypeSystemParser::parseModifyField(const ConditionalStreamReader &, } } if (fm.name().isEmpty()) { - m_error = msgMissingAttribute(nameAttribute()); + m_error = msgMissingAttribute(nameAttribute); return false; } m_contextStack.top()->fieldMods << fm; @@ -2572,21 +2602,25 @@ bool TypeSystemParser::parseAddFunction(const ConditionalStreamReader &, QString returnType; bool staticFunction = false; bool classMethod = false; + bool pythonOverride = false; QString access; for (auto i = attributes->size() - 1; i >= 0; --i) { const auto name = attributes->at(i).qualifiedName(); - if (name == signatureAttribute()) { + if (name == signatureAttribute) { originalSignature = attributes->takeAt(i).value().toString().simplified(); } else if (name == u"return-type") { returnType = attributes->takeAt(i).value().toString(); - } else if (name == staticAttribute()) { + } else if (name == staticAttribute) { staticFunction = convertBoolean(attributes->takeAt(i).value(), - staticAttribute(), false); - } else if (name == classmethodAttribute()) { + staticAttribute, false); + } else if (name == classmethodAttribute) { classMethod = convertBoolean(attributes->takeAt(i).value(), - classmethodAttribute(), false); - } else if (name == accessAttribute()) { + classmethodAttribute, false); + } else if (name == accessAttribute) { access = attributes->takeAt(i).value().toString(); + } else if (name == pythonOverrideAttribute) { + pythonOverride = convertBoolean(attributes->takeAt(i).value(), + pythonOverrideAttribute, false); } } @@ -2610,6 +2644,8 @@ bool TypeSystemParser::parseAddFunction(const ConditionalStreamReader &, func->setStatic(staticFunction); func->setClassMethod(classMethod); + func->setPythonOverride(pythonOverride); + func->setTargetLangPackage(m_defaultPackage); // Create signature for matching modifications signature = TypeDatabase::normalizedSignature(originalSignature); @@ -2651,7 +2687,7 @@ bool TypeSystemParser::parseAddPyMethodDef(const ConditionalStreamReader &, TypeSystemPyMethodDefEntry def; for (auto i = attributes->size() - 1; i >= 0; --i) { const auto name = attributes->at(i).qualifiedName(); - if (name == nameAttribute()) { + if (name == nameAttribute) { def.name = attributes->takeAt(i).value().toString(); } else if (name == u"doc") { def.doc = attributes->takeAt(i).value().toString(); @@ -2690,7 +2726,7 @@ bool TypeSystemParser::parseProperty(const ConditionalStreamReader &, StackEleme TypeSystemProperty property; for (auto i = attributes->size() - 1; i >= 0; --i) { const auto name = attributes->at(i).qualifiedName(); - if (name == nameAttribute()) { + if (name == nameAttribute) { property.name = attributes->takeAt(i).value().toString(); } else if (name == u"get") { property.read = attributes->takeAt(i).value().toString(); @@ -2698,10 +2734,10 @@ bool TypeSystemParser::parseProperty(const ConditionalStreamReader &, StackEleme property.type = attributes->takeAt(i).value().toString(); } else if (name == u"set") { property.write = attributes->takeAt(i).value().toString(); - } else if (name == generateGetSetDefAttribute()) { + } else if (name == generateGetSetDefAttribute) { property.generateGetSetDef = convertBoolean(attributes->takeAt(i).value(), - generateGetSetDefAttribute(), false); + generateGetSetDefAttribute, false); } } if (!property.isValid()) { @@ -2719,7 +2755,7 @@ bool TypeSystemParser::parseBasicModifyFunctionAttributes(QXmlStreamAttributes * { for (auto i = attributes->size() - 1; i >= 0; --i) { const auto name = attributes->at(i).qualifiedName(); - if (name == overloadNumberAttribute()) { + if (name == overloadNumberAttribute) { int overloadNumber = TypeSystem::OverloadNumberUnset; if (!parseOverloadNumber(attributes->takeAt(i), &overloadNumber, &m_error)) return false; @@ -2739,7 +2775,7 @@ bool TypeSystemParser::parseModifyFunctionAttributes(QXmlStreamAttributes *attri for (auto i = attributes->size() - 1; i >= 0; --i) { const auto name = attributes->at(i).qualifiedName(); - if (name == allowThreadAttribute()) { + if (name == allowThreadAttribute) { const QXmlStreamAttribute attribute = attributes->takeAt(i); const auto allowThreadOpt = allowThreadFromAttribute(attribute.value()); if (!allowThreadOpt.has_value()) { @@ -2747,7 +2783,7 @@ bool TypeSystemParser::parseModifyFunctionAttributes(QXmlStreamAttributes *attri return false; } mod->setAllowThread(allowThreadOpt.value()); - } else if (name == exceptionHandlingAttribute()) { + } else if (name == exceptionHandlingAttribute) { const auto attribute = attributes->takeAt(i); const auto exceptionOpt = exceptionHandlingFromAttribute(attribute.value()); if (!exceptionOpt.has_value()) { @@ -2755,7 +2791,7 @@ bool TypeSystemParser::parseModifyFunctionAttributes(QXmlStreamAttributes *attri return false; } mod->setExceptionHandling(exceptionOpt.value()); - } else if (name == snakeCaseAttribute()) { + } else if (name == snakeCaseAttribute) { const auto attribute = attributes->takeAt(i); const auto snakeCaseOpt = snakeCaseFromAttribute(attribute.value()); if (!snakeCaseOpt.has_value()) { @@ -2763,9 +2799,9 @@ bool TypeSystemParser::parseModifyFunctionAttributes(QXmlStreamAttributes *attri return false; } mod->setSnakeCase(snakeCaseOpt.value()); - } else if (name == deprecatedAttribute()) { + } else if (name == deprecatedAttribute) { const bool deprecated = convertBoolean(attributes->takeAt(i).value(), - deprecatedAttribute(), false); + deprecatedAttribute, false); mod->setModifierFlag(deprecated ? FunctionModification::Deprecated : FunctionModification::Undeprecated); } @@ -2796,15 +2832,15 @@ bool TypeSystemParser::parseModifyFunction(const ConditionalStreamReader &reader QString rename; for (auto i = attributes->size() - 1; i >= 0; --i) { const auto name = attributes->at(i).qualifiedName(); - if (name == signatureAttribute()) { + if (name == signatureAttribute) { originalSignature = attributes->takeAt(i).value().toString().simplified(); - } else if (name == accessAttribute()) { + } else if (name == accessAttribute) { access = attributes->takeAt(i).value().toString(); - } else if (name == renameAttribute()) { + } else if (name == renameAttribute) { rename = attributes->takeAt(i).value().toString(); - } else if (name == removeAttribute()) { + } else if (name == removeAttribute) { removed = convertRemovalAttribute(attributes->takeAt(i).value()); - } else if (name == virtualSlotAttribute() || name == threadAttribute()) { + } else if (name == virtualSlotAttribute || name == threadAttribute) { qCWarning(lcShiboken, "%s", qPrintable(msgUnimplementedAttributeWarning(reader, name))); } @@ -2844,7 +2880,7 @@ bool TypeSystemParser::parseModifyFunction(const ConditionalStreamReader &reader if (m == FunctionModification::NonFinal) { qCWarning(lcShiboken, "%s", qPrintable(msgUnimplementedAttributeValueWarning(reader, - accessAttribute(), access))); + accessAttribute, access))); } mod.setModifierFlag(m); } @@ -2891,7 +2927,7 @@ bool TypeSystemParser::parseReferenceCount(const ConditionalStreamReader &reader ReferenceCount rc; for (auto i = attributes->size() - 1; i >= 0; --i) { const auto name = attributes->at(i).qualifiedName(); - if (name == actionAttribute()) { + if (name == actionAttribute) { const QXmlStreamAttribute attribute = attributes->takeAt(i); const auto actionOpt = referenceCountFromAttribute(attribute.value()); if (!actionOpt.has_value()) { @@ -2928,11 +2964,11 @@ bool TypeSystemParser::parseParentOwner(const ConditionalStreamReader &, ArgumentOwner ao; for (auto i = attributes->size() - 1; i >= 0; --i) { const auto name = attributes->at(i).qualifiedName(); - if (name == indexAttribute()) { + if (name == indexAttribute) { const QString index = attributes->takeAt(i).value().toString(); if (!parseArgumentIndex(index, &ao.index, &m_error)) return false; - } else if (name == actionAttribute()) { + } else if (name == actionAttribute) { const auto action = attributes->takeAt(i); const auto actionOpt = argumentOwnerActionFromAttribute(action.value()); if (!actionOpt.has_value()) { @@ -2946,46 +2982,62 @@ bool TypeSystemParser::parseParentOwner(const ConditionalStreamReader &, return true; } -bool TypeSystemParser::readFileSnippet(QXmlStreamAttributes *attributes, CodeSnip *snip) +std::optional + TypeSystemParser::readFileSnippet(QXmlStreamAttributes *attributes) { - QString fileName; - QString snippetLabel; + Snippet result; for (auto i = attributes->size() - 1; i >= 0; --i) { const auto name = attributes->at(i).qualifiedName(); - if (name == u"file") { - fileName = attributes->takeAt(i).value().toString(); - } else if (name == snippetAttribute()) { - snippetLabel = attributes->takeAt(i).value().toString(); + if (name == fileAttribute) { + result.fileName = attributes->takeAt(i).value().toString(); + } else if (name == snippetAttribute) { + result.snippetLabel = attributes->takeAt(i).value().toString(); } } - if (fileName.isEmpty()) - return true; - const QString resolved = m_context->db->modifiedTypesystemFilepath(fileName, m_currentPath); + if (result.fileName.isEmpty()) { + m_error = "Snippet missing file name"_L1; + return std::nullopt; + } + const QString resolved = m_context->db->modifiedTypesystemFilepath(result.fileName, + m_currentPath); if (!QFile::exists(resolved)) { m_error = u"File for inject code not exist: "_s - + QDir::toNativeSeparators(fileName); - return false; + + QDir::toNativeSeparators(result.fileName); + return std::nullopt; } QFile codeFile(resolved); if (!codeFile.open(QIODevice::Text | QIODevice::ReadOnly)) { m_error = msgCannotOpenForReading(codeFile); - return false; + return std::nullopt; } - const auto codeOptional = extractSnippet(QString::fromUtf8(codeFile.readAll()), snippetLabel); + const auto contentOptional = extractSnippet(QString::fromUtf8(codeFile.readAll()), + result.snippetLabel); codeFile.close(); - if (!codeOptional.has_value()) { - m_error = msgCannotFindSnippet(resolved, snippetLabel); - return false; + if (!contentOptional.has_value()) { + m_error = msgCannotFindSnippet(resolved, result.snippetLabel); + return std::nullopt; } + result.content = contentOptional.value(); + return result; +} + +bool TypeSystemParser::readCodeSnippet(QXmlStreamAttributes *attributes, CodeSnip *snip) +{ + if (!hasFileSnippetAttributes(attributes)) + return true; // Expecting inline content. + const auto snippetOptional = readFileSnippet(attributes); + if (!snippetOptional.has_value()) + return false; + const auto snippet = snippetOptional.value(); - QString source = fileName; - if (!snippetLabel.isEmpty()) - source += u" ("_s + snippetLabel + u')'; + QString source = snippet.fileName; + if (!snippet.snippetLabel.isEmpty()) + source += " ("_L1 + snippet.snippetLabel + u')'; QString content; QTextStream str(&content); str << "// ========================================================================\n" "// START of custom code block [file: " - << source << "]\n" << codeOptional.value() + << source << "]\n" << snippet.content << "// END of custom code block [file: " << source << "]\n// ========================================================================\n"; snip->addCode(content); @@ -3007,11 +3059,11 @@ bool TypeSystemParser::parseInjectCode(const ConditionalStreamReader &, TypeSystem::CodeSnipPosition position = TypeSystem::CodeSnipPositionBeginning; TypeSystem::Language lang = TypeSystem::TargetLangCode; CodeSnip snip; - if (!readFileSnippet(attributes, &snip)) + if (!readCodeSnippet(attributes, &snip)) return false; for (auto i = attributes->size() - 1; i >= 0; --i) { const auto name = attributes->at(i).qualifiedName(); - if (name == classAttribute()) { + if (name == classAttribute) { const auto attribute = attributes->takeAt(i); const auto langOpt = languageFromAttribute(attribute.value()); if (!langOpt.has_value()) { @@ -3019,7 +3071,7 @@ bool TypeSystemParser::parseInjectCode(const ConditionalStreamReader &, return false; } lang = langOpt.value(); - } else if (name == positionAttribute()) { + } else if (name == positionAttribute) { const auto attribute = attributes->takeAt(i); const auto positionOpt = codeSnipPositionFromAttribute(attribute.value()); if (!positionOpt.has_value()) { @@ -3060,9 +3112,9 @@ bool TypeSystemParser::parseInclude(const ConditionalStreamReader &, Include::IncludeType location = Include::IncludePath; for (auto i = attributes->size() - 1; i >= 0; --i) { const auto name = attributes->at(i).qualifiedName(); - if (name == fileNameAttribute()) { + if (name == fileNameAttribute) { fileName = attributes->takeAt(i).value().toString(); - } else if (name == locationAttribute()) { + } else if (name == locationAttribute) { const auto attribute = attributes->takeAt(i); const auto locationOpt = locationFromAttribute(attribute.value()); if (!locationOpt.has_value()) { @@ -3092,9 +3144,9 @@ bool TypeSystemParser::parseInclude(const ConditionalStreamReader &, bool TypeSystemParser::parseSystemInclude(const ConditionalStreamReader &, QXmlStreamAttributes *attributes) { - const auto index = indexOfAttribute(*attributes, fileNameAttribute()); + const auto index = indexOfAttribute(*attributes, fileNameAttribute); if (index == -1) { - m_error = msgMissingAttribute(fileNameAttribute()); + m_error = msgMissingAttribute(fileNameAttribute); return false; } TypeDatabase::instance()->addForceProcessSystemInclude(attributes->takeAt(index).value().toString()); @@ -3115,9 +3167,9 @@ TemplateInstance * "conversion-rule, native-to-target or add-conversion tags."_s; return nullptr; } - const auto nameIndex = indexOfAttribute(*attributes, nameAttribute()); + const auto nameIndex = indexOfAttribute(*attributes, nameAttribute); if (nameIndex == -1) { - m_error = msgMissingAttribute(nameAttribute()); + m_error = msgMissingAttribute(nameAttribute); return nullptr; } return new TemplateInstance(attributes->takeAt(nameIndex).value().toString()); @@ -3136,7 +3188,7 @@ bool TypeSystemParser::parseReplace(const ConditionalStreamReader &, const auto name = attributes->at(i).qualifiedName(); if (name == u"from") from = attributes->takeAt(i).value().toString(); - else if (name == toAttribute()) + else if (name == toAttribute) to = attributes->takeAt(i).value().toString(); } m_templateInstance->addReplaceRule(from, to); @@ -3192,12 +3244,12 @@ bool TypeSystemParser::startElement(const ConditionalStreamReader &reader, Stack VersionRange versionRange; for (auto i = attributes.size() - 1; i >= 0; --i) { const auto name = attributes.at(i).qualifiedName(); - if (name == sinceAttribute()) { + if (name == sinceAttribute) { if (!parseVersion(attributes.takeAt(i).value().toString(), m_defaultPackage, &versionRange.since, &m_error)) { return false; } - } else if (name == untilAttribute()) { + } else if (name == untilAttribute) { if (!parseVersion(attributes.takeAt(i).value().toString(), m_defaultPackage, &versionRange.until, &m_error)) { return false; @@ -3244,11 +3296,11 @@ bool TypeSystemParser::startElement(const ConditionalStreamReader &reader, Stack if (isTypeEntry(element)) { QString name; if (element != StackElement::FunctionTypeEntry) { - const auto nameIndex = indexOfAttribute(attributes, nameAttribute()); + const auto nameIndex = indexOfAttribute(attributes, nameAttribute); if (nameIndex != -1) { name = attributes.takeAt(nameIndex).value().toString(); } else if (element != StackElement::EnumTypeEntry) { // anonymous enum? - m_error = msgMissingAttribute(nameAttribute()); + m_error = msgMissingAttribute(nameAttribute); return false; } } @@ -3261,7 +3313,7 @@ bool TypeSystemParser::startElement(const ConditionalStreamReader &reader, Stack if (m_context->db->hasDroppedTypeEntries()) { const QString identifier = element == StackElement::FunctionTypeEntry - ? attributes.value(signatureAttribute()).toString().simplified() : name; + ? attributes.value(signatureAttribute).toString().simplified() : name; if (shouldDropTypeEntry(m_context->db, m_contextStack, identifier)) { m_currentDroppedEntryDepth = 1; if (ReportHandler::isDebug(ReportHandler::SparseDebug)) { @@ -3293,7 +3345,7 @@ bool TypeSystemParser::startElement(const ConditionalStreamReader &reader, Stack if (element == StackElement::EnumTypeEntry) { const auto enumIdentifiedByIndex = - indexOfAttribute(attributes, enumIdentifiedByValueAttribute()); + indexOfAttribute(attributes, enumIdentifiedByValueAttribute); const QString identifiedByValue = enumIdentifiedByIndex != -1 ? attributes.takeAt(enumIdentifiedByIndex).value().toString() : QString(); if (name.isEmpty()) { @@ -3440,10 +3492,10 @@ bool TypeSystemParser::startElement(const ConditionalStreamReader &reader, Stack const auto topParent = m_stack.value(m_stack.size() - 3, StackElement::None); if (isTypeEntry(topParent)) { - const auto replaceIndex = indexOfAttribute(attributes, replaceAttribute()); + const auto replaceIndex = indexOfAttribute(attributes, replaceAttribute); const bool replace = replaceIndex == -1 || convertBoolean(attributes.takeAt(replaceIndex).value(), - replaceAttribute(), true); + replaceAttribute, true); auto customConversion = CustomConversion::getCustomConversion(top->entry); if (!customConversion) { m_error = msgMissingCustomConversion(top->entry); @@ -3470,7 +3522,7 @@ bool TypeSystemParser::startElement(const ConditionalStreamReader &reader, Stack return false; break; case StackElement::SuppressedWarning: { - const auto textIndex = indexOfAttribute(attributes, textAttribute()); + const auto textIndex = indexOfAttribute(attributes, textAttribute); if (textIndex == -1) { qCWarning(lcShiboken) << "Suppressed warning with no text specified"; } else { @@ -3559,9 +3611,9 @@ bool TypeSystemParser::startElement(const ConditionalStreamReader &reader, Stack return false; break; case StackElement::Template: { - const auto nameIndex = indexOfAttribute(attributes, nameAttribute()); + const auto nameIndex = indexOfAttribute(attributes, nameAttribute); if (nameIndex == -1) { - m_error = msgMissingAttribute(nameAttribute()); + m_error = msgMissingAttribute(nameAttribute); return false; } m_templateEntry.reset(new TemplateEntry(attributes.takeAt(nameIndex).value().toString())); diff --git a/sources/shiboken6/ApiExtractor/typesystemparser_p.h b/sources/shiboken6/ApiExtractor/typesystemparser_p.h index ebec00c2..4d9d4fd9 100644 --- a/sources/shiboken6/ApiExtractor/typesystemparser_p.h +++ b/sources/shiboken6/ApiExtractor/typesystemparser_p.h @@ -15,6 +15,7 @@ #include #include +#include QT_FORWARD_DECLARE_CLASS(QVersionNumber) QT_FORWARD_DECLARE_CLASS(QXmlStreamAttributes) @@ -146,6 +147,13 @@ public: QString errorString() const { return m_error; } private: + struct Snippet + { + QString content; + QString fileName; + QString snippetLabel; + }; + bool parseXml(ConditionalStreamReader &reader); bool setupSmartPointerInstantiations(); bool startElement(const ConditionalStreamReader &reader, StackElement element); @@ -248,7 +256,8 @@ private: QXmlStreamAttributes *); bool parseParentOwner(const ConditionalStreamReader &, StackElement topElement, QXmlStreamAttributes *); - bool readFileSnippet(QXmlStreamAttributes *attributes, CodeSnip *snip); + std::optional readFileSnippet(QXmlStreamAttributes *attributes); + bool readCodeSnippet(QXmlStreamAttributes *attributes, CodeSnip *snip); bool parseInjectCode(const ConditionalStreamReader &, StackElement topElement, QXmlStreamAttributes *); bool parseInclude(const ConditionalStreamReader &, StackElement topElement, const TypeEntryPtr &entry, QXmlStreamAttributes *); diff --git a/sources/shiboken6/ApiExtractor/typesystemtypeentry.h b/sources/shiboken6/ApiExtractor/typesystemtypeentry.h index bd69058d..9b967069 100644 --- a/sources/shiboken6/ApiExtractor/typesystemtypeentry.h +++ b/sources/shiboken6/ApiExtractor/typesystemtypeentry.h @@ -5,6 +5,7 @@ #define TYPESYSTEMTYPEENTRY_H #include "typesystem.h" +#include "modifications_typedefs.h" #include "typesystem_enums.h" #include "typesystem_typedefs.h" @@ -23,6 +24,15 @@ public: CodeSnipList &codeSnips(); void addCodeSnip(const CodeSnip &codeSnip); + QString subModuleOf() const; + void setSubModule(const QString &); + + const QString &namespaceBegin() const; + void setNamespaceBegin(const QString &n); + + const QString &namespaceEnd() const; + void setNamespaceEnd(const QString &n); + protected: explicit TypeSystemTypeEntry(TypeEntryPrivate *d); }; diff --git a/sources/shiboken6/ApiExtractor/xmlutils_libxslt.cpp b/sources/shiboken6/ApiExtractor/xmlutils_libxslt.cpp index c5f361c9..5a9a2691 100644 --- a/sources/shiboken6/ApiExtractor/xmlutils_libxslt.cpp +++ b/sources/shiboken6/ApiExtractor/xmlutils_libxslt.cpp @@ -139,7 +139,8 @@ QString LibXmlXQuery::doEvaluate(const QString &xPathExpression, QString *errorM std::shared_ptr libXml_createXQuery(const QString &focus, QString *errorMessage) { - XmlDocUniquePtr doc(xmlParseFile(QFile::encodeName(focus).constData())); + XmlDocUniquePtr doc(xmlReadFile(QFile::encodeName(focus).constData(), + "utf-8", XML_PARSE_NOENT)); if (!doc) { *errorMessage = u"libxml2: Cannot set focus to "_s + QDir::toNativeSeparators(focus); return {}; @@ -154,20 +155,22 @@ std::shared_ptr libXml_createXQuery(const QString &focus, QString *error // XSLT transformation -static const char xsltPrefix[] = R"( +static constexpr auto xsltPrefix = R"( -)"; +)"_L1; QString libXslt_transform(const QString &xml, QString xsl, QString *errorMessage) { ensureInitialized(); // Read XML data if (!xsl.startsWith(u""_s); } const QByteArray xmlData = xml.toUtf8(); - XmlDocUniquePtr xmlDoc(xmlParseMemory(xmlData.constData(), xmlData.size())); + + XmlDocUniquePtr xmlDoc(xmlReadMemory(xmlData.constData(), int(xmlData.size()), + "", "utf-8", XML_PARSE_NOENT)); if (!xmlDoc) { *errorMessage = u"xmlParseMemory() failed for XML."_s; return xml; diff --git a/sources/shiboken6/cmake/ShibokenHelpers.cmake b/sources/shiboken6/cmake/ShibokenHelpers.cmake index 1a46e5b2..8bc06610 100644 --- a/sources/shiboken6/cmake/ShibokenHelpers.cmake +++ b/sources/shiboken6/cmake/ShibokenHelpers.cmake @@ -206,7 +206,7 @@ macro(get_python_extension_suffix) # it is unable to set Python_CONFIG i.e. find `python3-config` script # This workaround sets the Python_SOABI manually for this platform. if(CMAKE_SYSTEM_NAME STREQUAL "Android" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "armv7-a") - set(Python_SOABI "cpython-310}") + set(Python_SOABI "cpython-311}") endif() if(NOT Python_SOABI) message(FATAL_ERROR "Python_SOABI variable is empty.") @@ -257,7 +257,7 @@ macro(shiboken_check_if_limited_api) libs = r'${Python_LIBRARIES}' libs = libs.split(';') for lib in libs: - if '\\\\' in lib and Path(lib).is_file(): + if ('\\\\' in lib or '/' in lib) and Path(lib).is_file(): lib = Path(lib) prefix = lib.parent py = lib.name diff --git a/sources/shiboken6/doc/_static/css/qt_style.css b/sources/shiboken6/doc/_static/css/qt_style.css index f174a6c5..08c4646c 100644 --- a/sources/shiboken6/doc/_static/css/qt_style.css +++ b/sources/shiboken6/doc/_static/css/qt_style.css @@ -1,3 +1,17 @@ +.text-center { + text-align: center !important; +} + +.text-center img { + padding-top: 10px; + height: 70px !important; +} + +.cover-img img { + object-fit: cover; + height: 50%; +} + /* Tables */ .section .docutils.container td { float:left; @@ -40,3 +54,47 @@ table.docutils td ul > li { position:relative; overflow:visible } + +/* We cannot put a :download:`....` command inside + * a sphinx-design button, so we add some properties from the button + * to the download class to mimic it */ +code.download { + text-align: center; + color: var(--color-brand-primary); + display: block; + border-color: transparent; + background-color: transparent; + border: 1px solid var(--color-brand-primary) !important; + border-radius: 0.25rem; + font-size: 1rem; + font-weight: 400; + vertical-align: middle; + padding: .375rem .75rem; + user-select: none; + line-height: 1.5; + transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out; +} + +code.download:hover { + color: white; + background-color: var(--color-brand-primary); + border-color: var(--color-brand-primary); + text-decoration: none; + padding: .375rem .75rem; +} + +dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple):first-child > dt { + font-size: +2.25rem; + font-weight: 700; + color: #ff00dd; +} + +.theme-toggle svg{ + width: +1.25rem; + height: +2.25rem; +} + +.sd-card-title code span { + font-size: +1rem; + color: var(--color-brand-primary); +} diff --git a/sources/shiboken6/doc/examples/index.rst b/sources/shiboken6/doc/examples/index.rst index 7316125e..e3575bc0 100644 --- a/sources/shiboken6/doc/examples/index.rst +++ b/sources/shiboken6/doc/examples/index.rst @@ -12,3 +12,12 @@ C++ examples :link: ../../examples/example_samplebinding_samplebinding.html :img-top: ../images/icecream.png + .. grid-item-card:: Scriptable Application + :class-item: cover-img + :link: ../../examples/example_scriptableapplication_scriptableapplication.html + :img-top: ../../../_images/example_no_image.png + + .. grid-item-card:: Widget Binding + :class-item: cover-img + :link: ../../examples/example_widgetbinding_widgetbinding.html + :img-top: ../../../_images/example_no_image.png diff --git a/sources/shiboken6/doc/index.rst b/sources/shiboken6/doc/index.rst index db842134..68f96dde 100644 --- a/sources/shiboken6/doc/index.rst +++ b/sources/shiboken6/doc/index.rst @@ -88,6 +88,18 @@ Documentation Examples + .. grid-item-card:: + :class-item: text-center + + Generating Python stub files. + +++ + .. button-ref:: shiboken-genpyi + :color: primary + :outline: + :expand: + + shiboken6-genpyi + .. grid-item-card:: :class-item: text-center @@ -109,4 +121,5 @@ Documentation shibokenmodule.rst typesystem.rst examples/index.rst + shiboken-genpyi.rst considerations.rst diff --git a/sources/shiboken6/doc/shiboken-genpyi.rst b/sources/shiboken6/doc/shiboken-genpyi.rst new file mode 100644 index 00000000..44d0edb9 --- /dev/null +++ b/sources/shiboken6/doc/shiboken-genpyi.rst @@ -0,0 +1,32 @@ +.. _shiboken6-genpyi: + +shiboken6-genpyi +================ + +`shiboken6-genpyi` is a command line tool to generate Python stub files +(.pyi) for any shiboken binding-based module (not just PySide). Stub +files define signatures of all classes, methods (including overloads), +constants and enums of a module. Signatures also contain type hints. +This helps your module integrate with Python type checkers and IDEs. +For example, if you use any function from your module, your IDE's +function lookup feature will show you the function signature and its +parameters and return value including types. + + +Usage +----- + +To generate stub files for a module, run the following command: + +.. code-block:: bash + + shiboken6-genpyi [OPTIONS] + +where `` is a space-separated list of module names (the +modules must be importable from the working directory) and where +`[OPTIONS]` can be one of the following: + +* **--quiet**: Run the tool quietly without output to stdout. +* **--outpath **: Specify the output directory for the + generated stub files. If not specified, the stub files are generated + in the location of the module binary. diff --git a/sources/shiboken6/doc/shibokenmodule.rst b/sources/shiboken6/doc/shibokenmodule.rst index b31efbcd..2f1c6d16 100644 --- a/sources/shiboken6/doc/shibokenmodule.rst +++ b/sources/shiboken6/doc/shibokenmodule.rst @@ -116,6 +116,15 @@ To import the module: This method should be used **only** for debug purposes by developers. + .. function:: dumpTypeGraph(file_name) + + Dumps the inheritance graph of the types existing in libshiboken + to ``.dot`` file for use with `Graphviz `_. + +.. function:: dumpWrapperMap() + + Dumps the map of wrappers existing in libshiboken to standard error. + .. py:class:: VoidPtr(address, size = -1, writeable = 0) :param address: (PyBuffer, SbkObject, int, VoidPtr) diff --git a/sources/shiboken6/doc/typediscovery.rst b/sources/shiboken6/doc/typediscovery.rst new file mode 100644 index 00000000..76d3adf7 --- /dev/null +++ b/sources/shiboken6/doc/typediscovery.rst @@ -0,0 +1,145 @@ +.. _typediscovery: + +************** +Type Discovery +************** + +When converting objects which are part of a class hierarchy from a pointer to a +base class, it is expected to get the Python type of the actual, most derived +type, as opposed to C++ which requires a cast for this: + +.. code-block:: python + + def event(self, event): + if event.type() == QEvent.Type.MousePress: + self.do_things(event.position()) + ... + + +.. code-block:: c++ + + bool event(QEvent *event) override + { + if (event->type() == QEvent::MousePress) { + auto *mouseEvent = static_cast(event); + doThings(mouseEvent->position()); + ... + } + +The process of determining the type of the event is called `type discovery`. + +Shiboken generates code to automatically detect the type. First, it tries to +find a converter for the name obtained by ``typeid(*pointer).name()``. This +should normally work as this name is registered by the binding. If that fails, +it starts walking a type inheritance graph built up in libshiboken to find the +most derived class by using a cast function (``dynamic_cast<>`` by default) to +check. + +For normal class hierarchies with virtual destructors, no special handling +is required since ``typeid()`` usually detects the proper class name. + +Multiple inheritance +==================== + +In case of multiple inheritance in C++, the conversion to the derived class is +not done in case it is not a single-line direct inheritance. For example, in +Qt, the class ``QWidget`` inherits both ``QObject`` (base of the ``QObject`` +hierarchy) and ``QPaintDevice``. + +When calling a function returning a ``QPaintDevice *``, for example +``QPainter.device()``, a Python type representing ``QPaintDevice`` is returned +instead of the underlying widget type. This restriction exists because the +underlying pointer in C++ is a pointer to a ``QPaintDevice *`` and differs from +the pointer to the ``QWidget``. + +Hierarchies of classes with non-virtual destructors +=================================================== + +There are some hierarchies of value-ish C++ classes that do not have virtual +destructors. This makes type discovery based on ``typeid()`` and +``dynamic_cast<>`` impossible. + +Examples in Qt are the ``QStyleOption``-derived or the ``QGradient`` +-derived classes. + +For such classes, some attributes need to be specified on the type entries: + +Primarily, a :ref:`polymorphic-id-expression` attribute +must be specified to be used as a check replacing ``dynamic_cast<>``. + +In addition, a :ref:`polymorphic-name-function` attribute can be specified. +This replaces the type name guess obtained by ``typeid()`` and is mainly a hint +to speed things up by skipping the checks for each type in the inheritance +graph. + +A :ref:`polymorphic-base` attribute identifies the base class of a hierarchy. +It should be given in case the base class inherits from another class to +prevent the logic from going below the base class. + +Using type discovery attributes for class hierarchies with virtual destructors +============================================================================== + +It is possible to use :ref:`polymorphic-id-expression` and +:ref:`polymorphic-name-function` for normal class hierarchies with virtual +destructors as well since they basically replace ``typeid()`` and +``dynamic_cast<>``. This makes sense if expressions can be specified that are +faster than the checks on virtual tables. + +Specifying :ref:`polymorphic-base` can also make sense for generating special +cast functions in case of multiple inheritance. For example, in Qt, +``QWindow``, ``QLayout``, ``QWidget`` are base classes of hierarchies. Since +they all inherit from ``QObject``, indicating the base classes prevents +the logic from using ``QObject`` as a base class. + +.. _typediscovery-attributes: + +Type discovery attributes reference +=================================== + +The following attributes related to type discovery may be be specified on the +:ref:`object-type` or :ref:`value-type` elements: + +.. _polymorphic-id-expression: + +polymorphic-id-expression ++++++++++++++++++++++++++ + +The **polymorphic-id-expression** attribute specifies an expression checking +whether a base class pointer is of the matching type. For example, in a +``virtual eventHandler(BaseEvent *e)`` function, this is used to construct a +Python wrapper matching the derived class (for example, a ``MouseEvent`` or +similar). The attribute value may contain placeholders: + +%1 + Fully qualified class name + +%B + Fully qualified name of the base class (found by base class + search or as indicated by **polymorphic-base**). + +To check for a class inheriting ``BaseEvent``, specify: + +.. code-block:: xml + + + +.. _polymorphic-name-function: + +polymorphic-name-function ++++++++++++++++++++++++++ + +The **polymorphic-name-function** attribute specifies the name of a function +returning the type name of a derived class on the base class type entry. +Normally, ``typeid(ptr).name()`` is used for this. + +The function is expected to return ``const char *``. + +.. _polymorphic-base: + +polymorphic-base +++++++++++++++++ + +The boolean **polymorphic-base** attribute indicates whether the class is the +base class of a class hierarchy. It is used for the *%B* placeholder in +**polymorphic-id-expression** and for cast operations in multiple inheritance. diff --git a/sources/shiboken6/doc/typesystem.rst b/sources/shiboken6/doc/typesystem.rst index e1e4fdda..26f92980 100644 --- a/sources/shiboken6/doc/typesystem.rst +++ b/sources/shiboken6/doc/typesystem.rst @@ -65,3 +65,4 @@ Extra options and Python caveats typesystem_solving_compilation.rst typesystem_specialfunctions.rst + typediscovery.rst diff --git a/sources/shiboken6/doc/typesystem_codeinjection.rst b/sources/shiboken6/doc/typesystem_codeinjection.rst index d0a5a039..03d5f4b1 100644 --- a/sources/shiboken6/doc/typesystem_codeinjection.rst +++ b/sources/shiboken6/doc/typesystem_codeinjection.rst @@ -74,6 +74,9 @@ function. | |shell |declaration|Used only for virtual functions. This code is injected at the | | | | |top. | | | +-----------+--------------------------------------------------------------+ +| | |override |Used only for virtual functions. The code is injected before | +| | | |the code calling the Python override. | +| | +-----------+--------------------------------------------------------------+ | | |beginning |Used only for virtual functions. The code is injected when the| | | | |function does not has a Python implementation, then the code | | | | |is inserted before c++ call | diff --git a/sources/shiboken6/doc/typesystem_converters.rst b/sources/shiboken6/doc/typesystem_converters.rst index 7bdabc49..ab6fba93 100644 --- a/sources/shiboken6/doc/typesystem_converters.rst +++ b/sources/shiboken6/doc/typesystem_converters.rst @@ -233,61 +233,3 @@ Variables & Functions **%CHECKTYPE[CPPTYPE]** Replaced by a |project| type checking function for a Python variable. The C++ type is indicated by ``CPPTYPE``. - - -.. _oldconverters: - -Converting The Old Converters -============================= - -If you use |project| for your bindings, and has defined some type conversions -using the ``Shiboken::Converter`` template, then you must update your converters -to the new scheme. - -Previously your conversion rules were declared in one line, like this: - - -.. code-block:: xml - - - - - - - -And implemented in a separate C++ file, like this: - - -.. code-block:: c++ - - namespace Shiboken { - template<> struct Converter - { - static inline bool checkType(PyObject* pyObj) { - return PyComplex_Check(pyObj); - } - static inline bool isConvertible(PyObject* pyObj) { - return PyComplex_Check(pyObj); - } - static inline PyObject* toPython(void* cppobj) { - return toPython(*reinterpret_cast(cppobj)); - } - static inline PyObject* toPython(const Complex& cpx) { - return PyComplex_FromDoubles(cpx.real(), cpx.imag()); - } - static inline Complex toCpp(PyObject* pyobj) { - double real = PyComplex_RealAsDouble(pyobj); - double imag = PyComplex_ImagAsDouble(pyobj); - return Complex(real, imag); - } - }; - } - - -In this case, the parts of the implementation that will be used in the new -conversion-rule are the ones in the two last method -``static inline PyObject* toPython(const Complex& cpx)`` and -``static inline Complex toCpp(PyObject* pyobj)``. The ``isConvertible`` method -is gone, and the ``checkType`` is now an attribute of the :ref:`add-conversion ` -tag. Refer back to the first example in this page and you will be able to -correlate the above template with the new scheme of conversion rule definition. diff --git a/sources/shiboken6/doc/typesystem_documentation.rst b/sources/shiboken6/doc/typesystem_documentation.rst index 504d2017..4e7d18b9 100644 --- a/sources/shiboken6/doc/typesystem_documentation.rst +++ b/sources/shiboken6/doc/typesystem_documentation.rst @@ -11,7 +11,8 @@ documentation. This node is a child of the :ref:`object-type`, .. code-block:: xml - + // the documentation @@ -24,8 +25,18 @@ occur and it accepts the following values: * native: Before XML<->Backend transformation occur, so the injected code *must* be a valid XML. * target: After XML<->Backend transformation occur, so the injected code *must* be a valid backend format. +The optional ``file`` attribute specifies the file name +(see :ref:`external-snippets`). + +The optional ``snippet`` attribute specifies the snippet label +(see :ref:`external-snippets`). + At the moment the only supported backend is Sphinx. +If the injected documentation contains a Sphinx function directive, no +directive will be auto-generated. This can be used to add parameter +documentation to added functions. + modify-documentation ^^^^^^^^^^^^^^^^^^^^ diff --git a/sources/shiboken6/doc/typesystem_manipulating_objects.rst b/sources/shiboken6/doc/typesystem_manipulating_objects.rst index 54792e49..e024cdf0 100644 --- a/sources/shiboken6/doc/typesystem_manipulating_objects.rst +++ b/sources/shiboken6/doc/typesystem_manipulating_objects.rst @@ -84,8 +84,8 @@ placeholders. Using Snippets From External Files ================================== -Code snippets can also be retrieved from external files found in the -typesystem search path (see :ref:`typesystem-paths`). +Code or documentation snippets can also be retrieved from external +files found in the typesystem search path (see :ref:`typesystem-paths`). .. code-block:: xml @@ -282,6 +282,7 @@ logic. This can be done using the :ref:`inject-code` node. access="public | protected" overload-number="number" static="yes | no" classmethod="yes | no" + python-override ="yes | no" since="..."/> @@ -320,6 +321,10 @@ within the `signature` field See :ref:`sequence-protocol` for adding the respective functions. +The *optional* attribute ``python-override`` indicates a special type +of added function, a python-override that will be generated into +the native wrapper (see :ref:`modifying-virtual-functions`). + .. _declare-function: declare-function @@ -500,3 +505,52 @@ configuration (see also option :ref:`drop-type-entries`) intended for building several configurations from one generated source tree, but still requires listing the correct source files in the ``CMakeLists.txt`` file. + +.. _modifying-virtual-functions: + +Modifying virtual functions +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Some C++ virtual functions are unsuitable for Python bindings: + +.. code-block:: c + + virtual void getInt(int *result) const; + +In that case, you would modify it to return the integer instead (or a tuple +in case of several out-parameters): + +.. code-block:: c + + virtual int getInt() const; + +For the binding itself, use the common argument modifications (removing +arguments, modifying return types with injected code snippets) to modify the +signature. + +To make it possible to reimplement the function in Python with the modified +signature, add a ``python-override`` function with that signature, using an +arbitrary name for disambiguation: + +.. code-block:: xml + + + +This causes a static function performing the call into Python for the override +to be generated into the native wrapper. + +In the existing virtual function, inject a code snippet at the ``shell`` / +``override`` position which calls the newly added function. The first 2 +arguments are the `Global interpreter lock handle` (``Shiboken::GilState``) and +the Python method determined by the override check (``PyObject *``). The +snippet then converts the arguments and return values and returns after that: + +.. code-block:: xml + + + + *result = getIntPyOverride(gil, pyOverride.object()); + return; + + diff --git a/sources/shiboken6/doc/typesystem_specifying_types.rst b/sources/shiboken6/doc/typesystem_specifying_types.rst index 406d5165..f65b79bb 100644 --- a/sources/shiboken6/doc/typesystem_specifying_types.rst +++ b/sources/shiboken6/doc/typesystem_specifying_types.rst @@ -43,14 +43,21 @@ It can have a number of attributes, described below. .. code-block:: xml - + The **package** attribute is a string describing the package to be used, e.g. "QtCore". -The *optional* **default-superclass** attribute is the canonical C++ base class -name of all objects, e.g., "object". + +The *optional* **submodule-of** attribute specifies the name of a module to +which the module is added as a sub-module. This requires adapting the +installation directory of the module accordingly. The *optional* attributes **allow-thread** and **exception-handling** specify the default handling for the corresponding function modification @@ -72,6 +79,13 @@ limitations to this though: exist (as is the case for example for ``QFileInfo::exists()``), the snake case name must be used. +The *optional* **namespace-begin** and **namespace-end** attributes will be +generated around the forward declarations in the module header. This is +intended for libraries which can optionally use inline namespaces +to allow for linking several versions of them together. +For example, for *Qt*, one would specify ``QT_BEGIN_NAMESPACE``, +``QT_END_NAMESPACE``, respectively. + .. _load-typesystem: load-typesystem @@ -139,8 +153,8 @@ already specified in the QtCore typesystem (see :ref:`primitive-cpp-types`). until="..." target-lang-api-name="..." default-constructor="..." - preferred-conversion="yes | no" /> - view-on="..." + preferred-conversion="yes | no" + view-on="..." /> The **name** attribute is the name of the primitive in C++. @@ -266,6 +280,7 @@ and it is a child of the :ref:`typesystem` node. Use flags="yes | no" flags-revision="..." cpp-type = "..." + doc-file = "..." python-type = "IntEnum | IntFlag" lower-bound="..." upper-bound="..." @@ -300,6 +315,11 @@ The *optional* **cpp-type** attribute specifies a C++ to be used for casting values. This can be useful for large values triggering MSVC signedness issues. +The *optional* **doc-file** attribute specifies the base name of the C++ or +``.qdoc`` file indicated by ``\relates`` or ``\headerfile`` in ``qdoc``, to +which the documentation of the enumeration is to be added and displayed in the +case its a global enumeration. This attribute is for ``qdoc`` only. + The **revision** attribute can be used to specify a revision for each type, easing the production of ABI compatible bindings. @@ -433,7 +453,9 @@ or other type nodes and may contain :ref:`add-function`, :ref:`add-pymethoddef`, parent-management="yes | no" polymorphic-id-expression="..." polymorphic-name-function="..." + polymorphic-base="yes | no" private="yes | no" + qt-metaobject="yes | no" qt-register-metatype = "yes | no | base" stream="yes | no" revision="..." @@ -461,6 +483,12 @@ parameter, or similar). For the *optional* **private** attribute, see :ref:`private_types`. +The *optional* **qt-metaobject** attribute specifies whether +the special Qt virtual functions ``metaObject()``, +``metaCall()``, and ``metaCast()`` are generated. For classes +using dynamic meta objects, for example, ``QDBusInterface``, +it can be turned off. + The *optional* **qt-register-metatype** attribute determines whether a Qt meta type registration is generated for ``name *``. By default, this is only generated for non-QObject types for usage @@ -508,18 +536,8 @@ type system has this attribute set, the heuristics will be applied to all classes. In shiboken 7, it will be mandatory to set the attribute. -The *optional* **polymorphic-id-expression** attribute specifies an -expression checking whether a base class pointer is of the matching -type. For example, in a ``virtual eventHandler(BaseEvent *e)`` -function, this is used to construct a Python wrapper matching -the derived class (for example, a ``MouseEvent`` or similar). - -The *optional* **polymorphic-name-function** specifies the name of a -function returning the type name of a derived class on the base class -type entry. Normally, ``typeid(ptr).name()`` is used for this. -However, this fails if the type hierarchy does not have virtual functions. -In this case, a function is required which typically decides depending -on some type enumeration. +For the *optional* **polymorphic-id-expression**, **polymorphic-name-function** +and **polymorphic-base** attributes, see :ref:`typediscovery-attributes`. interface-type ^^^^^^^^^^^^^^ @@ -772,6 +790,7 @@ and may contain a :ref:`modify-function` child node. @@ -785,6 +804,11 @@ the function was introduced. The *optional* **rename** attribute is used to modify the function name. +The *optional* **doc-file** attribute specifies the base name of the C++ or +``.qdoc`` file indicated by ``\relates`` or ``\headerfile`` in ``qdoc``, to +which the documentation of the function is to be added and displayed in the +case its a global function. This attribute is for ``qdoc`` only. + For the *optional* attributes **allow-thread**, **exception-handling**, **overload-number** and **snake-case**, see :ref:`modify-function`. diff --git a/sources/shiboken6/generator/generator.cpp b/sources/shiboken6/generator/generator.cpp index 7785b8f9..a0132653 100644 --- a/sources/shiboken6/generator/generator.cpp +++ b/sources/shiboken6/generator/generator.cpp @@ -30,8 +30,8 @@ using namespace Qt::StringLiterals; -static const char ENABLE_PYSIDE_EXTENSIONS[] = "enable-pyside-extensions"; -static const char AVOID_PROTECTED_HACK[] = "avoid-protected-hack"; +static constexpr auto ENABLE_PYSIDE_EXTENSIONS = "enable-pyside-extensions"_L1; +static constexpr auto AVOID_PROTECTED_HACK = "avoid-protected-hack"_L1; struct GeneratorOptions { @@ -52,6 +52,9 @@ struct Generator::GeneratorPrivate GeneratorOptions Generator::GeneratorPrivate::m_options; +// Kept as a variable for a potential Qt-in-namespace support +QString Generator::m_gsp = "::"_L1; + Generator::Generator() : m_d(new GeneratorPrivate) { } @@ -89,9 +92,9 @@ bool Generator::setup(const ApiExtractorResult &api) QList Generator::options() { return { - {QLatin1StringView(AVOID_PROTECTED_HACK), + {AVOID_PROTECTED_HACK, u"Avoid the use of the '#define protected public' hack."_s}, - {QLatin1StringView(ENABLE_PYSIDE_EXTENSIONS), + {ENABLE_PYSIDE_EXTENSIONS, u"Enable PySide extensions, such as support for signal/slots,\n" "use this if you are creating a binding for a Qt-based library."_s} }; @@ -112,9 +115,9 @@ bool GeneratorOptionsParser::handleBoolOption(const QString & key, OptionSource { if (source == OptionSource::CommandLineSingleDash) return false; - if (key == QLatin1StringView(ENABLE_PYSIDE_EXTENSIONS)) + if (key == ENABLE_PYSIDE_EXTENSIONS) return ( m_options->usePySideExtensions = true); - if (key == QLatin1StringView(AVOID_PROTECTED_HACK)) + if (key == AVOID_PROTECTED_HACK) return ( m_options->avoidProtectedHack = true); return false; } @@ -228,10 +231,9 @@ QString Generator::getFileNameBaseForSmartPointer(const AbstractMetaType &smartP const AbstractMetaType innerType = smartPointerType.getSmartPointerInnerType(); smartPointerType.typeEntry()->qualifiedCppName(); QString fileName = smartPointerType.typeEntry()->qualifiedCppName().toLower(); - fileName.replace(u"::"_s, u"_"_s); - fileName.append(u"_"_s); + fileName.append(u'_'); fileName.append(innerType.name().toLower()); - + fileName.replace(u"::"_s, u"_"_s); // std::shared_ptr return fileName; } @@ -308,9 +310,7 @@ QString Generator::getFullTypeName(TypeEntryCPtr type) QString result = type->qualifiedCppName(); if (type->isArray()) type = std::static_pointer_cast(type)->nestedTypeEntry(); - if (!isCppPrimitive(type)) - result.prepend(u"::"_s); - return result; + return isCppPrimitive(type) ? result : addGlobalScopePrefix(result); } QString Generator::getFullTypeName(const AbstractMetaType &type) @@ -320,7 +320,7 @@ QString Generator::getFullTypeName(const AbstractMetaType &type) if (type.isVoidPointer()) return u"void*"_s; if (type.typeEntry()->isContainer()) - return u"::"_s + type.cppSignature(); + return addGlobalScopePrefix(type.cppSignature()); QString typeName; if (type.typeEntry()->isComplex() && type.hasInstantiations()) typeName = getFullTypeNameWithoutModifiers(type); @@ -331,7 +331,9 @@ QString Generator::getFullTypeName(const AbstractMetaType &type) QString Generator::getFullTypeName(const AbstractMetaClassCPtr &metaClass) { - return u"::"_s + metaClass->qualifiedCppName(); + const QString &qualName = metaClass->qualifiedCppName(); + // Typedefs are generated into the global namespace + return metaClass->isTypeDef() ? qualName : addGlobalScopePrefix(qualName); } QString Generator::getFullTypeNameWithoutModifiers(const AbstractMetaType &type) @@ -357,7 +359,7 @@ QString Generator::getFullTypeNameWithoutModifiers(const AbstractMetaType &type) } while (typeName.endsWith(u'*') || typeName.endsWith(u' ')) typeName.chop(1); - return u"::"_s + typeName; + return addGlobalScopePrefix(typeName); } std::optional @@ -645,6 +647,16 @@ QString Generator::subDirectoryForPackage(QString packageNameIn) const return packageNameIn; } +QString Generator::addGlobalScopePrefix(const QString &t) +{ + return t.startsWith("std::"_L1) ? t : m_gsp + t; +} + +QString Generator::globalScopePrefix(const GeneratorContext &classContext) +{ + return classContext.useWrapper() ? QString{} : m_gsp; +} + template static QString getClassTargetFullName_(T t, bool includePackageName) { diff --git a/sources/shiboken6/generator/generator.h b/sources/shiboken6/generator/generator.h index 2b8784a3..5b051b59 100644 --- a/sources/shiboken6/generator/generator.h +++ b/sources/shiboken6/generator/generator.h @@ -214,6 +214,11 @@ protected: */ virtual QString subDirectoryForPackage(QString packageName = QString()) const; + static QString addGlobalScopePrefix(const QString &t); + static QString globalScopePrefix(const GeneratorContext &classContext); + + static QString m_gsp; + private: struct GeneratorPrivate; GeneratorPrivate *m_d; diff --git a/sources/shiboken6/generator/qtdoc/qtdocgenerator.cpp b/sources/shiboken6/generator/qtdoc/qtdocgenerator.cpp index bba60fa9..9fb5b9bf 100644 --- a/sources/shiboken6/generator/qtdoc/qtdocgenerator.cpp +++ b/sources/shiboken6/generator/qtdoc/qtdocgenerator.cpp @@ -46,6 +46,19 @@ using namespace Qt::StringLiterals; +static inline QString classScope(const AbstractMetaClassCPtr &metaClass) +{ + return metaClass->fullName(); +} + +struct DocPackage +{ + QStringList classPages; + QStringList decoratorPages; + AbstractMetaFunctionCList globalFunctions; + AbstractMetaEnumList globalEnums; +}; + struct DocGeneratorOptions { QtXmlToSphinxParameters parameters; @@ -69,8 +82,7 @@ struct GeneratorDocumentation AbstractMetaFunctionCPtr notify; }; - AbstractMetaFunctionCList constructors; - AbstractMetaFunctionCList allFunctions; // Except constructors + AbstractMetaFunctionCList allFunctions; AbstractMetaFunctionCList tocNormalFunctions; // Index lists AbstractMetaFunctionCList tocVirtuals; AbstractMetaFunctionCList tocSignalFunctions; @@ -86,19 +98,18 @@ static bool operator<(const GeneratorDocumentation::Property &lhs, return lhs.name < rhs.name; } -static QString propertyRefTarget(const AbstractMetaClassCPtr &cppClass, const QString &name) +static QString propertyRefTarget(const QString &name) { - QString result = cppClass->fullName() + u'.' + name; - result.replace(u"::"_s, u"."_s); + QString result = name; // For sphinx referencing, disambiguate the target from the getter name - // by inserting an invisible "Hangul choseong filler" character. - result.insert(1, QChar(0x115F)); + // by appending an invisible "Hangul choseong filler" character. + result.append(QChar(0x115F)); return result; } -static inline QString additionalDocumentationOption() { return QStringLiteral("additional-documentation"); } +constexpr auto additionalDocumentationOption = "additional-documentation"_L1; -static inline QString none() { return QStringLiteral("None"); } +constexpr auto none = "None"_L1; static bool shouldSkip(const AbstractMetaFunctionCPtr &func) { @@ -135,7 +146,14 @@ static bool shouldSkip(const AbstractMetaFunctionCPtr &func) static bool functionSort(const AbstractMetaFunctionCPtr &func1, const AbstractMetaFunctionCPtr &func2) { - return func1->name() < func2->name(); + const bool ctor1 = func1->isConstructor(); + if (ctor1 != func2->isConstructor()) + return ctor1; + const QString &name1 = func1->name(); + const QString &name2 = func2->name(); + if (name1 != name2) + return name1 < name2; + return func1->arguments().size() < func2->arguments().size(); } static inline QVersionNumber versionOf(const TypeEntryCPtr &te) @@ -148,77 +166,136 @@ static inline QVersionNumber versionOf(const TypeEntryCPtr &te) return {}; } -// Format a documentation reference (meth/attr): ":meth:`name`" -// We do not use the short form ":meth:`~target`" since that adds parentheses () -// for functions where we list the parameters instead. struct docRef { - explicit docRef(const char *kind, const QString &name, - const AbstractMetaClassCPtr &cppClass) : - m_kind(kind), m_name(name), m_cppClass(cppClass) {} + explicit docRef(const char *kind, QAnyStringView name) : + m_kind(kind), m_name(name) {} const char *m_kind; - const QString &m_name; - const AbstractMetaClassCPtr m_cppClass; + QAnyStringView m_name; }; static TextStream &operator<<(TextStream &s, const docRef &dr) { - QString className = dr.m_cppClass->fullName(); - className.replace(u"::"_s, u"."_s); - s << ':' << dr.m_kind << ":`" << dr.m_name << '<'; - if (!dr.m_name.startsWith(className)) - s << className << '.'; - s << dr.m_name << ">`"; + s << ':' << dr.m_kind << ":`" << dr.m_name << '`'; return s; } +static QString fileNameToTocEntry(const QString &fileName) +{ + constexpr auto rstSuffix = ".rst"_L1; + + QString result = fileName; + if (result.endsWith(rstSuffix)) + result.chop(rstSuffix.size()); // Remove the .rst extension + // skip namespace if necessary + auto lastDot = result.lastIndexOf(u'.'); + if (lastDot != -1) + result.remove(0, lastDot + 1); + return result; +} + +static void readExtraDoc(const QFileInfo &fi, + const QString &moduleName, + const QString &outputDir, + DocPackage *docPackage, QStringList *extraTocEntries) +{ + // Strip to "Property.rst" in output directory + const QString newFileName = fi.fileName().mid(moduleName.size() + 1); + QFile sourceFile(fi.absoluteFilePath()); + if (!sourceFile.open(QIODevice::ReadOnly|QIODevice::Text)) { + qCWarning(lcShibokenDoc, "%s", qPrintable(msgCannotOpenForReading(sourceFile))); + return; + } + const QByteArray contents = sourceFile.readAll(); + sourceFile.close(); + QFile targetFile(outputDir + u'/' + newFileName); + if (!targetFile.open(QIODevice::WriteOnly|QIODevice::Text)) { + qCWarning(lcShibokenDoc, "%s", qPrintable(msgCannotOpenForWriting(targetFile))); + return; + } + targetFile.write(contents); + if (contents.contains("decorator::")) + docPackage->decoratorPages.append(newFileName); + else + docPackage->classPages.append(newFileName); + extraTocEntries->append(fileNameToTocEntry(newFileName)); +} + // Format a short documentation reference (automatically dropping the prefix // by using '~'), usable for property/attributes ("attr"). struct shortDocRef { - explicit shortDocRef(const char *kind, const QString &target) : - m_kind(kind), m_target(target) {} + explicit shortDocRef(const char *kind, QAnyStringView name) : + m_kind(kind), m_name(name) {} const char *m_kind; - const QString &m_target; + QAnyStringView m_name; }; static TextStream &operator<<(TextStream &s, const shortDocRef &sdr) { - s << ':' << sdr.m_kind << ":`~" << sdr.m_target << '`'; + s << ':' << sdr.m_kind << ":`~" << sdr.m_name << '`'; return s; } struct functionRef : public docRef { - explicit functionRef(const QString &name, const AbstractMetaClassCPtr &cppClass) : - docRef("meth", name, cppClass) {} + explicit functionRef(QAnyStringView name) : docRef("meth", name) {} +}; + +struct classRef : public shortDocRef +{ + explicit classRef(QAnyStringView name) : shortDocRef("class", name) {} +}; + +struct propRef : public shortDocRef // Attribute/property (short) reference +{ + explicit propRef(const QString &target) : + shortDocRef("attr", target) {} }; -struct functionTocEntry // Format a TOC entry for a function +struct headline { - explicit functionTocEntry(const AbstractMetaFunctionCPtr& func, - const AbstractMetaClassCPtr &cppClass) : - m_func(func), m_cppClass(cppClass) {} + explicit headline(QAnyStringView title, char underLineChar = '-') : + m_title(title), m_underLineChar(underLineChar) {} - AbstractMetaFunctionCPtr m_func; - const AbstractMetaClassCPtr m_cppClass; + QAnyStringView m_title; + char m_underLineChar; }; -static TextStream &operator<<(TextStream &s, const functionTocEntry &ft) +static TextStream &operator<<(TextStream &s, const headline &h) { - s << functionRef(QtDocGenerator::getFuncName(ft.m_func), ft.m_cppClass) - << ' ' << QtDocGenerator::formatArgs(ft.m_func); + s << h.m_title << '\n' << Pad(h.m_underLineChar, h.m_title.size()) << "\n\n"; return s; } -struct propRef : public shortDocRef // Attribute/property (short) reference +struct pyClass { - explicit propRef(const QString &target) : - shortDocRef("attr", target) {} + explicit pyClass(QAnyStringView name) : m_name(name) {} + + QAnyStringView m_name; +}; + +static TextStream &operator<<(TextStream &s, pyClass c) +{ + s << ".. py:class:: " << c.m_name << "\n\n"; + return s; +} + +struct currentModule +{ + explicit currentModule(QAnyStringView module) : m_module(module) {} + + QAnyStringView m_module; }; +static TextStream &operator<<(TextStream &s, const currentModule &m) +{ + s << ".. currentmodule:: " << m.m_module << "\n\n\n"; + return s; +} + DocGeneratorOptions QtDocGenerator::m_options; QtDocGenerator::QtDocGenerator() @@ -248,28 +325,23 @@ QString QtDocGenerator::fileNameForContext(const GeneratorContext &context) cons } void QtDocGenerator::writeFormattedBriefText(TextStream &s, const Documentation &doc, - const AbstractMetaClassCPtr &metaclass) const + const QString &scope) const { - writeFormattedText(s, doc.brief(), doc.format(), metaclass); + writeFormattedText(s, doc.brief(), doc.format(), scope); } void QtDocGenerator::writeFormattedDetailedText(TextStream &s, const Documentation &doc, - const AbstractMetaClassCPtr &metaclass) const + const QString &scope) const { - writeFormattedText(s, doc.detailed(), doc.format(), metaclass); + writeFormattedText(s, doc.detailed(), doc.format(), scope); } void QtDocGenerator::writeFormattedText(TextStream &s, const QString &doc, Documentation::Format format, - const AbstractMetaClassCPtr &metaClass) const + const QString &scope) const { - QString metaClassName; - - if (metaClass) - metaClassName = metaClass->fullName(); - if (format == Documentation::Native) { - QtXmlToSphinx x(this, m_options.parameters, doc, metaClassName); + QtXmlToSphinx x(this, m_options.parameters, doc, scope); s << x; } else { const auto lines = QStringView{doc}.split(u'\n'); @@ -300,7 +372,7 @@ static void writeInheritanceList(TextStream &s, const AbstractMetaClassCList& cl for (qsizetype i = 0, size = classes.size(); i < size; ++i) { if (i > 0) s << ", "; - s << ":ref:`" << classes.at(i)->name() << '`'; + s << classRef(classes.at(i)->fullName()); } s << "\n\n"; } @@ -337,21 +409,18 @@ void QtDocGenerator::generateClass(TextStream &s, const GeneratorContext &classC AbstractMetaClassCPtr metaClass = classContext.metaClass(); qCDebug(lcShibokenDoc).noquote().nospace() << "Generating Documentation for " << metaClass->fullName(); - m_packages[metaClass->package()] << fileNameForContext(classContext); + m_packages[metaClass->package()].classPages << fileNameForContext(classContext); m_docParser->setPackageName(metaClass->package()); m_docParser->fillDocumentation(std::const_pointer_cast(metaClass)); - QString className = metaClass->name(); - s << ".. _" << className << ":" << "\n\n"; - s << ".. currentmodule:: " << metaClass->package() << "\n\n\n"; - - s << className << '\n'; - s << Pad('*', className.size()) << "\n\n"; + s << currentModule(metaClass->package()) << pyClass(metaClass->name()); + Indentation indent(s); auto documentation = metaClass->documentation(); + const QString scope = classScope(metaClass); if (documentation.hasBrief()) - writeFormattedBriefText(s, documentation, metaClass); + writeFormattedBriefText(s, documentation, scope); if (!metaClass->baseClasses().isEmpty()) { if (m_options.inheritanceDiagram) { @@ -373,13 +442,13 @@ void QtDocGenerator::generateClass(TextStream &s, const GeneratorContext &classC const GeneratorDocumentation doc = generatorDocumentation(metaClass); if (!doc.allFunctions.isEmpty() || !doc.properties.isEmpty()) { - s << "\nSynopsis\n--------\n\n"; - writePropertyToc(s, doc, metaClass); - writeFunctionToc(s, u"Functions"_s, metaClass, doc.tocNormalFunctions); - writeFunctionToc(s, u"Virtual functions"_s, metaClass, doc.tocVirtuals); - writeFunctionToc(s, u"Slots"_s, metaClass, doc.tocSlotFunctions); - writeFunctionToc(s, u"Signals"_s, metaClass, doc.tocSignalFunctions); - writeFunctionToc(s, u"Static functions"_s, metaClass, doc.tocStaticFunctions); + s << '\n' << headline("Synopsis"); + writePropertyToc(s, doc); + writeFunctionToc(s, u"Methods"_s, doc.tocNormalFunctions); + writeFunctionToc(s, u"Virtual methods"_s, doc.tocVirtuals); + writeFunctionToc(s, u"Slots"_s, doc.tocSlotFunctions); + writeFunctionToc(s, u"Signals"_s, doc.tocSignalFunctions); + writeFunctionToc(s, u"Static functions"_s, doc.tocStaticFunctions); } s << "\n.. note::\n" @@ -389,64 +458,52 @@ void QtDocGenerator::generateClass(TextStream &s, const GeneratorContext &classC " translation, you can also let us know by creating a ticket on\n" " https:/bugreports.qt.io/projects/PYSIDE\n\n"; - s << "\nDetailed Description\n" - "--------------------\n\n" - << ".. _More:\n"; + s << '\n' << headline("Detailed Description") << ".. _More:\n"; - writeInjectDocumentation(s, TypeSystem::DocModificationPrepend, metaClass, nullptr); - if (!writeInjectDocumentation(s, TypeSystem::DocModificationReplace, metaClass, nullptr)) - writeFormattedDetailedText(s, documentation, metaClass); + writeInjectDocumentation(s, TypeSystem::DocModificationPrepend, metaClass); + if (!writeInjectDocumentation(s, TypeSystem::DocModificationReplace, metaClass)) + writeFormattedDetailedText(s, documentation, scope); + writeInjectDocumentation(s, TypeSystem::DocModificationAppend, metaClass); - if (!metaClass->isNamespace()) - writeConstructors(s, metaClass, doc.constructors); + writeEnums(s, metaClass->enums(), scope); if (!doc.properties.isEmpty()) writeProperties(s, doc, metaClass); - writeEnums(s, metaClass); if (!metaClass->isNamespace()) writeFields(s, metaClass); - QString lastName; - for (const auto &func : std::as_const(doc.allFunctions)) { - const bool indexed = func->name() != lastName; - lastName = func->name(); - s << (func->isStatic() ? ".. py:staticmethod:: " : ".. py:method:: "); - writeFunction(s, metaClass, func, indexed); - } - - writeInjectDocumentation(s, TypeSystem::DocModificationAppend, metaClass, nullptr); + writeFunctions(s, doc.allFunctions, metaClass, scope); } void QtDocGenerator::writeFunctionToc(TextStream &s, const QString &title, - const AbstractMetaClassCPtr &cppClass, const AbstractMetaFunctionCList &functions) { if (!functions.isEmpty()) { - s << title << '\n' - << Pad('^', title.size()) << '\n'; - - s << ".. container:: function_list\n\n" << indent; - for (const auto &func : functions) - s << "* def " << functionTocEntry(func, cppClass) << '\n'; + s << headline(title, '^') + << ".. container:: function_list\n\n" << indent; + // Functions are sorted by the Metabuilder; erase overloads + QStringList toc; + toc.reserve(functions.size()); + std::transform(functions.cbegin(), functions.end(), + std::back_inserter(toc), getFuncName); + toc.erase(std::unique(toc.begin(), toc.end()), toc.end()); + for (const auto &func : toc) + s << "* def " << functionRef(func) << '\n'; s << outdent << "\n\n"; } } void QtDocGenerator::writePropertyToc(TextStream &s, - const GeneratorDocumentation &doc, - const AbstractMetaClassCPtr &cppClass) + const GeneratorDocumentation &doc) { if (doc.properties.isEmpty()) return; - const QString title = u"Properties"_s; - s << title << '\n' - << Pad('^', title.size()) << '\n'; - - s << ".. container:: function_list\n\n" << indent; + s << headline("Properties", '^') + << ".. container:: function_list\n\n" << indent; for (const auto &prop : doc.properties) { - s << "* " << propRef(propertyRefTarget(cppClass, prop.name)); + s << "* " << propRef(propertyRefTarget(prop.name)); if (prop.documentation.hasBrief()) s << " - " << prop.documentation.brief(); s << '\n'; @@ -462,32 +519,33 @@ void QtDocGenerator::writeProperties(TextStream &s, << "``from __feature__ import true_property`` is used or via accessor " << "functions otherwise.\n\n"; + const QString scope = classScope(cppClass); for (const auto &prop : doc.properties) { const QString type = translateToPythonType(prop.type, cppClass, /* createRef */ false); - s << ".. py:property:: " << propertyRefTarget(cppClass, prop.name) + s << ".. py:property:: " << propertyRefTarget(prop.name) << "\n :type: " << type << "\n\n\n"; if (!prop.documentation.isEmpty()) - writeFormattedText(s, prop.documentation.detailed(), Documentation::Native, cppClass); + writeFormattedText(s, prop.documentation.detailed(), Documentation::Native, scope); s << "**Access functions:**\n"; if (prop.getter) - s << " * " << functionTocEntry(prop.getter, cppClass) << '\n'; + s << " * " << functionRef(prop.getter->name()) << '\n'; if (prop.setter) - s << " * " << functionTocEntry(prop.setter, cppClass) << '\n'; + s << " * " << functionRef(prop.setter->name()) << '\n'; if (prop.reset) - s << " * " << functionTocEntry(prop.reset, cppClass) << '\n'; + s << " * " << functionRef(prop.reset->name()) << '\n'; if (prop.notify) - s << " * Signal " << functionTocEntry(prop.notify, cppClass) << '\n'; + s << " * Signal " << functionRef(prop.notify->name()) << '\n'; s << '\n'; } } -void QtDocGenerator::writeEnums(TextStream &s, const AbstractMetaClassCPtr &cppClass) const +void QtDocGenerator::writeEnums(TextStream &s, const AbstractMetaEnumList &enums, + const QString &scope) const { - static const QString section_title = u".. attribute:: "_s; - - for (const AbstractMetaEnum &en : cppClass->enums()) { - s << section_title << cppClass->fullName() << '.' << en.name() << "\n\n"; - writeFormattedDetailedText(s, en.documentation(), cppClass); + for (const AbstractMetaEnum &en : enums) { + s << pyClass(en.name()); + Indentation indent(s); + writeFormattedDetailedText(s, en.documentation(), scope); const auto version = versionOf(en.typeEntry()); if (!version.isNull()) s << rstVersionAdded(version); @@ -497,64 +555,15 @@ void QtDocGenerator::writeEnums(TextStream &s, const AbstractMetaClassCPtr &cppC void QtDocGenerator::writeFields(TextStream &s, const AbstractMetaClassCPtr &cppClass) const { - static const QString section_title = u".. attribute:: "_s; + constexpr auto section_title = ".. attribute:: "_L1; + const QString scope = classScope(cppClass); for (const AbstractMetaField &field : cppClass->fields()) { s << section_title << cppClass->fullName() << "." << field.name() << "\n\n"; - writeFormattedDetailedText(s, field.documentation(), cppClass); + writeFormattedDetailedText(s, field.documentation(), scope); } } -void QtDocGenerator::writeConstructors(TextStream &s, const AbstractMetaClassCPtr &cppClass, - const AbstractMetaFunctionCList &constructors) const -{ - static const QString sectionTitle = u".. class:: "_s; - - bool first = true; - QHash arg_map; - - if (constructors.isEmpty()) { - s << sectionTitle << cppClass->fullName(); - } else { - QByteArray pad; - for (const auto &func : constructors) { - s << pad; - if (first) { - first = false; - s << sectionTitle; - pad = QByteArray(sectionTitle.size(), ' '); - } - s << functionSignature(cppClass, func) << "\n\n"; - - const auto version = versionOf(func->typeEntry()); - if (!version.isNull()) - s << pad << rstVersionAdded(version); - if (func->isDeprecated()) - s << pad << rstDeprecationNote("constructor"); - - const AbstractMetaArgumentList &arguments = func->arguments(); - for (const AbstractMetaArgument &arg : arguments) { - if (!arg_map.contains(arg.name())) { - arg_map.insert(arg.name(), arg); - } - } - } - } - - s << '\n'; - - for (auto it = arg_map.cbegin(), end = arg_map.cend(); it != end; ++it) { - s.indent(2); - writeParameterType(s, cppClass, it.value()); - s.outdent(2); - } - - s << '\n'; - - for (const auto &func : constructors) - writeFormattedDetailedText(s, func->documentation(), cppClass); -} - QString QtDocGenerator::formatArgs(const AbstractMetaFunctionCPtr &func) { QString ret = u"("_s; @@ -586,13 +595,13 @@ QString QtDocGenerator::formatArgs(const AbstractMetaFunctionCPtr &func) || defValue.startsWith(u"QList")) { defValue = u"list()"_s; } else if (defValue == u"QVariant()") { - defValue = none(); + defValue = none; } else { defValue.replace(u"::"_s, u"."_s); if (defValue == u"nullptr") - defValue = none(); + defValue = none; else if (defValue == u"0" && arg.type().isObject()) - defValue = none(); + defValue = none; } ret += u'=' + defValue; } @@ -660,25 +669,21 @@ void QtDocGenerator::writeDocSnips(TextStream &s, } } -bool QtDocGenerator::writeInjectDocumentation(TextStream &s, - TypeSystem::DocModificationMode mode, - const AbstractMetaClassCPtr &cppClass, - const AbstractMetaFunctionCPtr &func) +bool QtDocGenerator::writeDocModifications(TextStream &s, + const DocModificationList &mods, + TypeSystem::DocModificationMode mode, + const QString &scope) const { - Indentation indentation(s); bool didSomething = false; - - const DocModificationList mods = DocParser::getDocModifications(cppClass, func); - for (const DocModification &mod : mods) { if (mod.mode() == mode) { switch (mod.format()) { case TypeSystem::NativeCode: - writeFormattedText(s, mod.code(), Documentation::Native, cppClass); + writeFormattedText(s, mod.code(), Documentation::Native, scope); didSomething = true; break; case TypeSystem::TargetLangCode: - writeFormattedText(s, mod.code(), Documentation::Target, cppClass); + writeFormattedText(s, mod.code(), Documentation::Target, scope); didSomething = true; break; default: @@ -686,33 +691,46 @@ bool QtDocGenerator::writeInjectDocumentation(TextStream &s, } } } + return didSomething; +} +bool QtDocGenerator::writeInjectDocumentation(TextStream &s, + TypeSystem::DocModificationMode mode, + const AbstractMetaClassCPtr &cppClass) const +{ + const bool didSomething = + writeDocModifications(s, DocParser::getDocModifications(cppClass), + mode, classScope(cppClass)); s << '\n'; // FIXME PYSIDE-7: Deprecate the use of doc string on glue code. // This is pre "add-function" and "inject-documentation" tags. const TypeSystem::CodeSnipPosition pos = mode == TypeSystem::DocModificationPrepend ? TypeSystem::CodeSnipPositionBeginning : TypeSystem::CodeSnipPositionEnd; - if (func) - writeDocSnips(s, func->injectedCodeSnips(), pos, TypeSystem::TargetLangCode); - else - writeDocSnips(s, cppClass->typeEntry()->codeSnips(), pos, TypeSystem::TargetLangCode); + writeDocSnips(s, cppClass->typeEntry()->codeSnips(), pos, TypeSystem::TargetLangCode); return didSomething; } -QString QtDocGenerator::functionSignature(const AbstractMetaClassCPtr &cppClass, - const AbstractMetaFunctionCPtr &func) +bool QtDocGenerator::writeInjectDocumentation(TextStream &s, + TypeSystem::DocModificationMode mode, + const DocModificationList &modifications, + const AbstractMetaFunctionCPtr &func, + const QString &scope) const { - QString funcName = cppClass->fullName(); - if (!func->isConstructor()) - funcName += u'.' + getFuncName(func); + const bool didSomething = writeDocModifications(s, modifications, mode, scope); + s << '\n'; - return funcName + formatArgs(func); + // FIXME PYSIDE-7: Deprecate the use of doc string on glue code. + // This is pre "add-function" and "inject-documentation" tags. + const TypeSystem::CodeSnipPosition pos = mode == TypeSystem::DocModificationPrepend + ? TypeSystem::CodeSnipPositionBeginning : TypeSystem::CodeSnipPositionEnd; + writeDocSnips(s, func->injectedCodeSnips(), pos, TypeSystem::TargetLangCode); + return didSomething; } static QString inline toRef(const QString &t) { - return ":any:`"_L1 + t + u'`'; + return ":class:`~"_L1 + t + u'`'; } QString QtDocGenerator::translateToPythonType(const AbstractMetaType &type, @@ -720,7 +738,7 @@ QString QtDocGenerator::translateToPythonType(const AbstractMetaType &type, bool createRef) const { static const QStringList nativeTypes = - {boolT(), floatT(), intT(), pyObjectT(), pyStrT()}; + {boolT, floatT, intT, pyObjectT, pyStrT}; QString name = type.name(); if (nativeTypes.contains(name)) @@ -730,24 +748,24 @@ QString QtDocGenerator::translateToPythonType(const AbstractMetaType &type, const auto &basicName = basicReferencedTypeEntry(type.typeEntry())->name(); if (AbstractMetaType::cppSignedIntTypes().contains(basicName) || AbstractMetaType::cppUnsignedIntTypes().contains(basicName)) { - return intT(); + return intT; } if (AbstractMetaType::cppFloatTypes().contains(basicName)) - return floatT(); + return floatT; } static const QSet stringTypes = { u"uchar"_s, u"std::string"_s, u"std::wstring"_s, u"std::stringview"_s, u"std::wstringview"_s, - qStringT(), u"QStringView"_s, u"QAnyStringView"_s, u"QUtf8StringView"_s + qStringT, u"QStringView"_s, u"QAnyStringView"_s, u"QUtf8StringView"_s }; if (stringTypes.contains(name)) - return pyStrT(); + return pyStrT; static const QHash typeMap = { - { cPyObjectT(), pyObjectT() }, + { cPyObjectT, pyObjectT }, { u"QStringList"_s, u"list of strings"_s }, - { qVariantT(), pyObjectT() } + { qVariantT, pyObjectT } }; const auto found = typeMap.constFind(name); if (found != typeMap.cend()) @@ -755,8 +773,17 @@ QString QtDocGenerator::translateToPythonType(const AbstractMetaType &type, if (type.isFlags()) { const auto fte = std::static_pointer_cast(type.typeEntry()); - auto enumName = fte->originator()->targetLangName(); + auto enumTypeEntry = fte->originator(); + auto enumName = enumTypeEntry->targetLangName(); + if (createRef) + enumName.prepend(enumTypeEntry->targetLangPackage() + u'.'); return "Combination of "_L1 + (createRef ? toRef(enumName) : enumName); + } else if (type.isEnum()) { + auto enumTypeEntry = std::static_pointer_cast(type.typeEntry()); + auto enumName = enumTypeEntry->targetLangName(); + if (createRef) + enumName.prepend(enumTypeEntry->targetLangPackage() + u'.'); + return createRef ? toRef(enumName) : enumName; } if (type.isConstant() && name == "char"_L1 && type.indirections() == 1) @@ -782,13 +809,15 @@ QString QtDocGenerator::translateToPythonType(const AbstractMetaType &type, } if (auto k = AbstractMetaClass::findClass(api().classes(), type.typeEntry())) - return createRef ? toRef(k->fullName()) : k->fullName(); + return createRef ? toRef(k->fullName()) : k->name(); return createRef ? toRef(name) : name; } QString QtDocGenerator::getFuncName(const AbstractMetaFunctionCPtr &cppFunc) { + if (cppFunc->isConstructor()) + return "__init__"_L1; QString result = cppFunc->name(); if (cppFunc->isOperatorOverload()) { const QString pythonOperator = Generator::pythonOperatorFunctionName(result); @@ -832,16 +861,40 @@ void QtDocGenerator::writeFunctionParametersType(TextStream &s, s << '\n'; } -void QtDocGenerator::writeFunction(TextStream &s, const AbstractMetaClassCPtr &cppClass, - const AbstractMetaFunctionCPtr &func, bool indexed) +static bool containsFunctionDirective(const DocModification &dm) { - s << functionSignature(cppClass, func); + return dm.mode() != TypeSystem::DocModificationXPathReplace + && dm.code().contains(".. py:"_L1); +} - { +void QtDocGenerator::writeFunctions(TextStream &s, const AbstractMetaFunctionCList &funcs, + const AbstractMetaClassCPtr &cppClass, const QString &scope) +{ + QString lastName; + for (const auto &func : funcs) { + const bool indexed = func->name() != lastName; + lastName = func->name(); + writeFunction(s, func, cppClass, scope, indexed); + } +} + +void QtDocGenerator::writeFunction(TextStream &s, const AbstractMetaFunctionCPtr &func, + const AbstractMetaClassCPtr &cppClass, + const QString &scope, bool indexed) +{ + const auto modifications = DocParser::getDocModifications(func, cppClass); + + // Enable injecting parameter documentation by adding a complete function directive. + if (std::none_of(modifications.cbegin(), modifications.cend(), containsFunctionDirective)) { + if (func->ownerClass() == nullptr) + s << ".. py:function:: "; + else + s << (func->isStatic() ? ".. py:staticmethod:: " : ".. py:method:: "); + s << getFuncName(func) << formatArgs(func); Indentation indentation(s); if (!indexed) s << "\n:noindex:"; - if (func->attributes().testFlag(AbstractMetaFunction::Attribute::FinalCppMethod)) + if (func->cppAttributes().testFlag(FunctionAttribute::Final)) s << "\n:final:"; else if (func->isAbstract()) s << "\n:abstractmethod:"; @@ -853,16 +906,12 @@ void QtDocGenerator::writeFunction(TextStream &s, const AbstractMetaClassCPtr &c if (func->isDeprecated()) s << rstDeprecationNote("function"); } - writeInjectDocumentation(s, TypeSystem::DocModificationPrepend, cppClass, func); - if (!writeInjectDocumentation(s, TypeSystem::DocModificationReplace, cppClass, func)) { - writeFormattedBriefText(s, func->documentation(), cppClass); - writeFormattedDetailedText(s, func->documentation(), cppClass); - } - writeInjectDocumentation(s, TypeSystem::DocModificationAppend, cppClass, func); + + writeFunctionDocumentation(s, func, modifications, scope); if (auto propIndex = func->propertySpecIndex(); propIndex >= 0) { const QString name = cppClass->propertySpecs().at(propIndex).name(); - const QString target = propertyRefTarget(cppClass, name); + const QString target = propertyRefTarget(name); if (func->isPropertyReader()) s << "\nGetter of property " << propRef(target) << " .\n\n"; else if (func->isPropertyWriter()) @@ -874,23 +923,79 @@ void QtDocGenerator::writeFunction(TextStream &s, const AbstractMetaClassCPtr &c } } -static void writeFancyToc(TextStream& s, const QStringList& items) +void QtDocGenerator::writeFunctionDocumentation(TextStream &s, const AbstractMetaFunctionCPtr &func, + const DocModificationList &modifications, + const QString &scope) const + +{ + writeInjectDocumentation(s, TypeSystem::DocModificationPrepend, modifications, func, scope); + if (!writeInjectDocumentation(s, TypeSystem::DocModificationReplace, modifications, func, scope)) { + writeFormattedBriefText(s, func->documentation(), scope); + writeFormattedDetailedText(s, func->documentation(), scope); + } + writeInjectDocumentation(s, TypeSystem::DocModificationAppend, modifications, func, scope); +} + +static QStringList fileListToToc(const QStringList &items) +{ + QStringList result; + result.reserve(items.size()); + std::transform(items.cbegin(), items.cend(), std::back_inserter(result), + fileNameToTocEntry); + return result; +} + +static QStringList functionListToToc(const AbstractMetaFunctionCList &functions) +{ + QStringList result; + result.reserve(functions.size()); + for (const auto &f : functions) + result.append(f->name()); + // Functions are sorted by the Metabuilder; erase overloads + result.erase(std::unique(result.begin(), result.end()), result.end()); + return result; +} + +static QStringList enumListToToc(const AbstractMetaEnumList &enums) +{ + QStringList result; + result.reserve(enums.size()); + for (const auto &e : enums) + result.append(e.name()); + return result; +} + +// Sort entries for a TOC by first character, dropping the +// leading common Qt prefixes like 'Q'. +static QChar sortKey(const QString &key) +{ + const auto size = key.size(); + if (size >= 2 && (key.at(0) == u'Q' || key.at(0) == u'q') + && (key.at(1).isUpper() || key.at(1).isDigit())) { + return key.at(1); // "QClass" -> 'C', "qSin()" -> 'S', 'Q3DSurfaceWidget' -> '3' + } + if (size >= 3 && key.startsWith("Q_"_L1)) + return key.at(2).toUpper(); // "Q_ARG" -> 'A' + if (size >= 4 && key.startsWith("QT_"_L1)) + return key.at(3).toUpper(); // "QT_TR" -> 'T' + auto idx = 0; + for (; idx < size && key.at(idx) == u'_'; ++idx) { + } // "__init__" -> 'I' + return idx < size ? key.at(idx).toUpper() : u'A'; +} + +static void writeFancyToc(TextStream& s, QAnyStringView title, + const QStringList& items, + QLatin1StringView referenceType) { using TocMap = QMap; + + if (items.isEmpty()) + return; + TocMap tocMap; - QChar idx; - for (QString item : items) { - if (item.isEmpty()) - continue; - item.chop(4); // Remove the .rst extension - // skip namespace if necessary - const QString className = item.split(u'.').last(); - if (className.startsWith(u'Q') && className.length() > 1) - idx = className[1]; - else - idx = className[0]; - tocMap[idx] << item; - } + for (const QString &item : items) + tocMap[sortKey(item)] << item; static const qsizetype numColumns = 4; @@ -905,7 +1010,7 @@ static void writeFancyToc(TextStream& s, const QStringList& items) row.clear(); row << QtXmlToSphinx::TableCell(QString{}); } - const QString entry = u"* :doc:`"_s + item + u'`'; + const QString entry = "* :"_L1 + referenceType + ":`"_L1 + item + u'`'; row << QtXmlToSphinx::TableCell(entry); } if (row.size() > 1) @@ -913,13 +1018,24 @@ static void writeFancyToc(TextStream& s, const QStringList& items) } table.normalize(); - s << ".. container:: pysidetoc\n\n"; + s << '\n' << headline(title) << ".. container:: pysidetoc\n\n"; table.format(s); } bool QtDocGenerator::finishGeneration() { - if (!api().classes().isEmpty()) + for (const auto &f : api().globalFunctions()) { + auto ncf = std::const_pointer_cast(f); + m_docParser->fillGlobalFunctionDocumentation(ncf); + m_packages[f->targetLangPackage()].globalFunctions.append(f); + } + + for (auto e : api().globalEnums()) { + m_docParser->fillGlobalEnumDocumentation(e); + m_packages[e.typeEntry()->targetLangPackage()].globalEnums.append(e); + } + + if (!m_packages.isEmpty()) writeModuleDocumentation(); if (!m_options.additionalDocumentationList.isEmpty()) writeAdditionalDocumentation(); @@ -950,11 +1066,22 @@ bool QtDocGenerator::writeInheritanceFile() return true; } +// Remove function entries that have extra documentation pages +static inline void removeExtraDocs(const QStringList &extraTocEntries, + AbstractMetaFunctionCList *functions) +{ + auto predicate = [&extraTocEntries](const AbstractMetaFunctionCPtr &f) { + return extraTocEntries.contains(f->name()); + }; + functions->erase(std::remove_if(functions->begin(),functions->end(), predicate), + functions->end()); +} + void QtDocGenerator::writeModuleDocumentation() { - QMap::iterator it = m_packages.begin(); - for (; it != m_packages.end(); ++it) { - std::sort(it.value().begin(), it.value().end()); + for (auto it = m_packages.begin(), end = m_packages.end(); it != end; ++it) { + auto &docPackage = it.value(); + std::sort(docPackage.classPages.begin(), docPackage.classPages.end()); QString key = it.key(); key.replace(u'.', u'/'); @@ -963,9 +1090,7 @@ void QtDocGenerator::writeModuleDocumentation() TextStream& s = output.stream; const QString &title = it.key(); - s << ".. module:: " << title << "\n\n" - << title << '\n' - << Pad('*', title.length()) << "\n\n"; + s << ".. module:: " << title << "\n\n" << headline(title, '*'); // Store the it.key() in a QString so that it can be stripped off unwanted // information when neeeded. For example, the RST files in the extras directory @@ -976,6 +1101,7 @@ void QtDocGenerator::writeModuleDocumentation() moduleName.remove(0, lastIndex + 1); // Search for extra-sections + QStringList extraTocEntries; if (!m_options.extraSectionDir.isEmpty()) { QDir extraSectionDir(m_options.extraSectionDir); if (!extraSectionDir.exists()) { @@ -988,28 +1114,23 @@ void QtDocGenerator::writeModuleDocumentation() const QString filter = moduleName + u".?*.rst"_s; const auto fileList = extraSectionDir.entryInfoList({filter}, QDir::Files, QDir::Name); - for (const auto &fi : fileList) { - // Strip to "Property.rst" in output directory - const QString newFileName = fi.fileName().mid(moduleName.size() + 1); - it.value().append(newFileName); - const QString newFilePath = outputDir + u'/' + newFileName; - if (QFile::exists(newFilePath)) - QFile::remove(newFilePath); - if (!QFile::copy(fi.absoluteFilePath(), newFilePath)) { - qCDebug(lcShibokenDoc).noquote().nospace() << "Error copying extra doc " - << QDir::toNativeSeparators(fi.absoluteFilePath()) - << " to " << QDir::toNativeSeparators(newFilePath); - } - } + for (const auto &fi : fileList) + readExtraDoc(fi, moduleName, outputDir, &docPackage, &extraTocEntries); } + removeExtraDocs(extraTocEntries, &docPackage.globalFunctions); + const bool hasGlobals = !docPackage.globalFunctions.isEmpty() + || !docPackage.globalEnums.isEmpty(); + const QString globalsPage = moduleName + "_globals.rst"_L1; + s << ".. container:: hide\n\n" << indent << ".. toctree::\n" << indent << ":maxdepth: 1\n\n"; - for (const QString &className : std::as_const(it.value())) + if (hasGlobals) + s << globalsPage << '\n'; + for (const QString &className : std::as_const(docPackage.classPages)) s << className << '\n'; - s << "\n\n" << outdent << outdent - << "Detailed Description\n--------------------\n\n"; + s << "\n\n" << outdent << outdent << headline("Detailed Description"); // module doc is always wrong and C++istic, so go straight to the extra directory! QFile moduleDoc(m_options.extraSectionDir + u'/' + moduleName @@ -1030,14 +1151,43 @@ void QtDocGenerator::writeModuleDocumentation() } } - s << "\nList of Classes\n" - << "---------------\n\n"; - writeFancyToc(s, it.value()); + writeFancyToc(s, "List of Classes", fileListToToc(docPackage.classPages), + "class"_L1); + writeFancyToc(s, "List of Decorators", fileListToToc(docPackage.decoratorPages), + "deco"_L1); + writeFancyToc(s, "List of Functions", functionListToToc(docPackage.globalFunctions), + "py:func"_L1); + writeFancyToc(s, "List of Enumerations", enumListToToc(docPackage.globalEnums), + "any"_L1); output.done(); + + if (hasGlobals) + writeGlobals(it.key(), outputDir + u'/' + globalsPage, docPackage); } } +void QtDocGenerator::writeGlobals(const QString &package, + const QString &fileName, + const DocPackage &docPackage) +{ + FileOut output(fileName); + TextStream &s = output.stream; + + // Write out functions with injected documentation + if (!docPackage.globalFunctions.isEmpty()) { + s << currentModule(package) << headline("Functions"); + writeFunctions(s, docPackage.globalFunctions, {}, {}); + } + + if (!docPackage.globalEnums.isEmpty()) { + s << headline("Enumerations"); + writeEnums(s, docPackage.globalEnums, package); + } + + output.done(); +} + static inline QString msgNonExistentAdditionalDocFile(const QString &dir, const QString &fileName) { @@ -1075,8 +1225,8 @@ void QtDocGenerator::writeAdditionalDocumentation() const targetDir = outDir.absolutePath(); } else { if (!outDir.exists(dir) && !outDir.mkdir(dir)) { - const QString m = QStringLiteral("Cannot create directory ") - + dir + QStringLiteral(" under ") + const QString m = "Cannot create directory "_L1 + + dir + " under "_L1 + QDir::toNativeSeparators(outputDirectory()); throw Exception(m); } @@ -1162,7 +1312,7 @@ QList QtDocGenerator::options() u"Directory used to search for extra documentation sections"_s}, {u"library-source-dir="_s, u"Directory where library source code is located"_s}, - {additionalDocumentationOption() + u"="_s, + {additionalDocumentationOption + u"="_s, u"List of additional XML files to be converted to .rst files\n" "(for example, tutorials)."_s}, {u"inheritance-file="_s, @@ -1230,7 +1380,7 @@ bool QtDocGeneratorOptionsParser::handleOption(const QString &key, const QString m_options->doxygen = true; return true; } - if (key == additionalDocumentationOption()) { + if (key == additionalDocumentationOption) { m_options->additionalDocumentationList = value; return true; } @@ -1275,16 +1425,10 @@ GeneratorDocumentation GeneratorDocumentation result; const auto allFunctions = cppClass->functions(); result.allFunctions.reserve(allFunctions.size()); - for (const auto &func : allFunctions) { - if (!shouldSkip(func)) { - if (func->isConstructor()) - result.constructors.append(func); - else - result.allFunctions.append(func); - } - } + std::remove_copy_if(allFunctions.cbegin(), allFunctions.cend(), + std::back_inserter(result.allFunctions), shouldSkip); - std::sort(result.allFunctions.begin(), result.allFunctions.end(), functionSort); + std::stable_sort(result.allFunctions.begin(), result.allFunctions.end(), functionSort); for (const auto &func : std::as_const(result.allFunctions)) { if (func->isStatic()) @@ -1409,7 +1553,7 @@ QtXmlToSphinxLink QtDocGenerator::resolveLink(const QtXmlToSphinxLink &link) con { if (link.type != QtXmlToSphinxLink::Reference || !isRelativeHtmlFile(link.linkRef)) return link; - static const QString prefix = QStringLiteral("https://doc.qt.io/qt-") + static const QString prefix = "https://doc.qt.io/qt-"_L1 + QString::number(QT_VERSION_MAJOR) + u'/'; QtXmlToSphinxLink resolved = link; resolved.type = QtXmlToSphinxLink::External; diff --git a/sources/shiboken6/generator/qtdoc/qtdocgenerator.h b/sources/shiboken6/generator/qtdoc/qtdocgenerator.h index 094a9326..3b1c82e7 100644 --- a/sources/shiboken6/generator/qtdoc/qtdocgenerator.h +++ b/sources/shiboken6/generator/qtdoc/qtdocgenerator.h @@ -17,6 +17,7 @@ class DocParser; struct DocGeneratorOptions; struct GeneratorDocumentation; +struct DocPackage; /** * The DocGenerator generates documentation from library being binded. @@ -59,46 +60,52 @@ protected: bool finishGeneration() override; private: - void writeEnums(TextStream &s, const AbstractMetaClassCPtr &cppClass) const; + void writeEnums(TextStream &s, const AbstractMetaEnumList &enums, + const QString &scope) const; void writeFields(TextStream &s, const AbstractMetaClassCPtr &cppClass) const; - static QString functionSignature(const AbstractMetaClassCPtr &cppClass, - const AbstractMetaFunctionCPtr &func); - void writeFunction(TextStream &s, const AbstractMetaClassCPtr &cppClass, - const AbstractMetaFunctionCPtr &func, bool indexed = true); + void writeFunctions(TextStream &s, const AbstractMetaFunctionCList &funcs, + const AbstractMetaClassCPtr &cppClass, const QString &scope); + void writeFunction(TextStream &s, const AbstractMetaFunctionCPtr &func, + const AbstractMetaClassCPtr &cppClass = {}, + const QString &scope = {}, bool indexed = true); + void writeFunctionDocumentation(TextStream &s, const AbstractMetaFunctionCPtr &func, + const DocModificationList &modifications, + const QString &scope) const; void writeFunctionParametersType(TextStream &s, const AbstractMetaClassCPtr &cppClass, const AbstractMetaFunctionCPtr &func) const; static void writeFunctionToc(TextStream &s, const QString &title, - const AbstractMetaClassCPtr &cppClass, const AbstractMetaFunctionCList &functions); static void writePropertyToc(TextStream &s, - const GeneratorDocumentation &doc, - const AbstractMetaClassCPtr &cppClass); + const GeneratorDocumentation &doc); void writeProperties(TextStream &s, const GeneratorDocumentation &doc, const AbstractMetaClassCPtr &cppClass) const; void writeParameterType(TextStream &s, const AbstractMetaClassCPtr &cppClass, const AbstractMetaArgument &arg) const; - - void writeConstructors(TextStream &s, - const AbstractMetaClassCPtr &cppClass, - const AbstractMetaFunctionCList &constructors) const; - void writeFormattedText(TextStream &s, const QString &doc, Documentation::Format format, - const AbstractMetaClassCPtr &metaClass = {}) const; + const QString &scope = {}) const; void writeFormattedBriefText(TextStream &s, const Documentation &doc, - const AbstractMetaClassCPtr &metaclass = {}) const; + const QString &scope = {}) const; void writeFormattedDetailedText(TextStream &s, const Documentation &doc, - const AbstractMetaClassCPtr &metaclass = {}) const; + const QString &scope = {}) const; bool writeInjectDocumentation(TextStream &s, TypeSystem::DocModificationMode mode, - const AbstractMetaClassCPtr &cppClass, - const AbstractMetaFunctionCPtr &func); + const AbstractMetaClassCPtr &cppClass) const; + bool writeInjectDocumentation(TextStream &s, TypeSystem::DocModificationMode mode, + const DocModificationList &modifications, + const AbstractMetaFunctionCPtr &func, + const QString &scope = {}) const; + bool writeDocModifications(TextStream &s, const DocModificationList &mods, + TypeSystem::DocModificationMode mode, + const QString &scope = {}) const; static void writeDocSnips(TextStream &s, const CodeSnipList &codeSnips, TypeSystem::CodeSnipPosition position, TypeSystem::Language language); void writeModuleDocumentation(); + void writeGlobals(const QString &package, const QString &fileName, + const DocPackage &docPackage); void writeAdditionalDocumentation() const; bool writeInheritanceFile(); @@ -114,7 +121,7 @@ private: static GeneratorDocumentation generatorDocumentation(const AbstractMetaClassCPtr &cppClass); QStringList m_functionList; - QMap m_packages; + QMap m_packages; QScopedPointer m_docParser; static DocGeneratorOptions m_options; }; diff --git a/sources/shiboken6/generator/qtdoc/qtxmltosphinx.cpp b/sources/shiboken6/generator/qtdoc/qtxmltosphinx.cpp index b129a453..55c1d209 100644 --- a/sources/shiboken6/generator/qtdoc/qtxmltosphinx.cpp +++ b/sources/shiboken6/generator/qtdoc/qtxmltosphinx.cpp @@ -19,10 +19,6 @@ using namespace Qt::StringLiterals; -static inline QString nameAttribute() { return QStringLiteral("name"); } -static inline QString titleAttribute() { return QStringLiteral("title"); } -static inline QString fullTitleAttribute() { return QStringLiteral("fulltitle"); } - QString msgTagWarning(const QXmlStreamReader &reader, const QString &context, const QString &tag, const QString &message) { @@ -445,13 +441,13 @@ QString QtXmlToSphinx::popOutputBuffer() return result; } -static const QString autoTranslatedPlaceholder = u"AUTO_GENERATED\n"_s; -static const QString autoTranslatedNote = -uR"(.. warning:: +constexpr auto autoTranslatedPlaceholder = "AUTO_GENERATED\n"_L1; +constexpr auto autoTranslatedNote = +R"(.. warning:: This section contains snippets that were automatically translated from C++ to Python and may contain errors. -)"_s; +)"_L1; void QtXmlToSphinx::setAutoTranslatedNote(QString *str) const { @@ -778,9 +774,9 @@ void QtXmlToSphinx::handleParaTagEnd() { QString result = popOutputBuffer().simplified(); if (result.startsWith(u"**Warning:**")) - result.replace(0, 12, QStringLiteral(".. warning:: ")); + result.replace(0, 12, ".. warning:: "_L1); else if (result.startsWith(u"**Note:**")) - result.replace(0, 9, QStringLiteral(".. note:: ")); + result.replace(0, 9, ".. note:: "_L1); m_output << result << "\n\n"; } @@ -849,23 +845,23 @@ void QtXmlToSphinx::handleArgumentTag(QXmlStreamReader& reader) } } -static inline QString functionLinkType() { return QStringLiteral("function"); } -static inline QString classLinkType() { return QStringLiteral("class"); } +constexpr auto functionLinkType = "function"_L1; +constexpr auto classLinkType = "class"_L1; static inline QString fixLinkType(QStringView type) { // TODO: create a flag PROPERTY-AS-FUNCTION to ask if the properties // are recognized as such or not in the binding if (type == u"property") - return functionLinkType(); + return functionLinkType; if (type == u"typedef") - return classLinkType(); + return classLinkType; return type.toString(); } static inline QString linkSourceAttribute(const QString &type) { - if (type == functionLinkType() || type == classLinkType()) + if (type == functionLinkType || type == classLinkType) return u"raw"_s; return type == u"enum" || type == u"page" ? type : u"href"_s; @@ -891,7 +887,7 @@ void QtXmlToSphinx::handleSeeAlsoTag(QXmlStreamReader& reader) const QString text = textR.toString(); if (m_seeAlsoContext.isNull()) { const QString type = text.endsWith(u"()") - ? functionLinkType() : classLinkType(); + ? functionLinkType : classLinkType; m_seeAlsoContext.reset(handleLinkStart(type, text)); } handleLinkText(m_seeAlsoContext.data(), text); @@ -910,7 +906,7 @@ void QtXmlToSphinx::handleSeeAlsoTag(QXmlStreamReader& reader) } } -static inline QString fallbackPathAttribute() { return QStringLiteral("path"); } +constexpr auto fallbackPathAttribute = "path"_L1; template // const char*/class Indentor void formatSnippet(TextStream &str, Indent indent, const QString &snippet) @@ -950,8 +946,8 @@ void QtXmlToSphinx::handleSnippetTag(QXmlStreamReader& reader) QString location = reader.attributes().value(u"location"_s).toString(); QString identifier = reader.attributes().value(u"identifier"_s).toString(); QString fallbackPath; - if (reader.attributes().hasAttribute(fallbackPathAttribute())) - fallbackPath = reader.attributes().value(fallbackPathAttribute()).toString(); + if (reader.attributes().hasAttribute(fallbackPathAttribute)) + fallbackPath = reader.attributes().value(fallbackPathAttribute).toString(); QString errorMessage; const Snippet snippet = readSnippetFromLocations(location, identifier, @@ -1165,7 +1161,7 @@ QtXmlToSphinxLink *QtXmlToSphinx::handleLinkStart(const QString &type, QString r if (type == u"external" || isHttpLink(ref)) { result->type = QtXmlToSphinxLink::External; - } else if (type == functionLinkType() && !m_context.isEmpty()) { + } else if (type == functionLinkType && !m_context.isEmpty()) { result->type = QtXmlToSphinxLink::Method; const auto rawlinklist = QStringView{result->linkRef}.split(u'.'); if (rawlinklist.size() == 1 || rawlinklist.constFirst() == m_context) { @@ -1176,9 +1172,9 @@ QtXmlToSphinxLink *QtXmlToSphinx::handleLinkStart(const QString &type, QString r } else { result->linkRef = m_generator->expandFunction(result->linkRef); } - } else if (type == functionLinkType() && m_context.isEmpty()) { + } else if (type == functionLinkType && m_context.isEmpty()) { result->type = QtXmlToSphinxLink::Function; - } else if (type == classLinkType()) { + } else if (type == classLinkType) { result->type = QtXmlToSphinxLink::Class; result->linkRef = m_generator->expandClass(m_context, result->linkRef); } else if (type == u"enum") { @@ -1397,11 +1393,11 @@ void QtXmlToSphinx::handlePageTag(QXmlStreamReader &reader) m_output << disableIndent; - const auto title = reader.attributes().value(titleAttribute()); + const auto title = reader.attributes().value("title"); if (!title.isEmpty()) m_output << rstLabel(title.toString()); - const auto fullTitle = reader.attributes().value(fullTitleAttribute()); + const auto fullTitle = reader.attributes().value("fulltitle"); const int size = fullTitle.isEmpty() ? writeEscapedRstText(m_output, title) : writeEscapedRstText(m_output, fullTitle); @@ -1414,7 +1410,7 @@ void QtXmlToSphinx::handleTargetTag(QXmlStreamReader &reader) { if (reader.tokenType() != QXmlStreamReader::StartElement) return; - const auto name = reader.attributes().value(nameAttribute()); + const auto name = reader.attributes().value("name"); if (!name.isEmpty()) m_output << rstLabel(name.toString()); } diff --git a/sources/shiboken6/generator/shiboken/cppgenerator.cpp b/sources/shiboken6/generator/shiboken/cppgenerator.cpp index 1f3c68dd..a7013ade 100644 --- a/sources/shiboken6/generator/shiboken/cppgenerator.cpp +++ b/sources/shiboken6/generator/shiboken/cppgenerator.cpp @@ -58,6 +58,11 @@ using namespace Qt::StringLiterals; static const char shibokenErrorsOccurred[] = "Shiboken::Errors::occurred() != nullptr"; +static constexpr auto virtualMethodStaticReturnVar = "result"_L1; + +static constexpr auto sbkObjectTypeF = "SbkObject_TypeF()"_L1; +static const char initInheritanceFunction[] = "initInheritance"; + static QString mangleName(QString name) { if (name == u"None" || name == u"False" || name == u"True" || name == u"from") @@ -67,9 +72,9 @@ static QString mangleName(QString name) struct sbkUnusedVariableCast { - explicit sbkUnusedVariableCast(QStringView name) : m_name(name) {} + explicit sbkUnusedVariableCast(QAnyStringView name) : m_name(name) {} - const QStringView m_name; + const QAnyStringView m_name; }; TextStream &operator<<(TextStream &str, const sbkUnusedVariableCast &c) @@ -78,6 +83,24 @@ TextStream &operator<<(TextStream &str, const sbkUnusedVariableCast &c) return str; } +struct pyTypeGetSlot +{ + explicit pyTypeGetSlot(QAnyStringView funcType, QAnyStringView typeObject, + QAnyStringView aSlot) : + m_funcType(funcType), m_typeObject(typeObject), m_slot(aSlot) {} + + const QAnyStringView m_funcType; + const QAnyStringView m_typeObject; + const QAnyStringView m_slot; +}; + +TextStream &operator<<(TextStream &str, const pyTypeGetSlot &p) +{ + str << "reinterpret_cast<" << p.m_funcType << ">(PepType_GetSlot(" + << p.m_typeObject << ", " << p.m_slot << "));\n"; + return str; +} + TextStream &operator<<(TextStream &s, CppGenerator::ErrorReturn r) { s << "return"; @@ -98,6 +121,25 @@ TextStream &operator<<(TextStream &s, CppGenerator::ErrorReturn r) return s; } +static constexpr auto converterVar = "converter"_L1; + +struct registerConverterName +{ + explicit registerConverterName(QAnyStringView typeName, + QAnyStringView varName = converterVar) : + m_typeName(typeName), m_varName(varName) {} + + QAnyStringView m_typeName; + QAnyStringView m_varName; +}; + +TextStream &operator<<(TextStream &s, const registerConverterName &r) +{ + s << "Shiboken::Conversions::registerConverterName(" << r.m_varName + << ", \"" << r.m_typeName << "\");\n"; + return s; +} + // Protocol function name / function parameters / return type struct ProtocolEntry { @@ -130,7 +172,7 @@ const ProtocolEntries &mappingProtocols() u"PyObject*"_s}, {u"__msetitem__"_s, u"PyObject *self, PyObject *_key, PyObject *_value"_s, - intT()}}; + intT}}; return result; } @@ -147,16 +189,16 @@ const ProtocolEntries &sequenceProtocols() u"PyObject*"_s}, {u"__setitem__"_s, u"PyObject *self, Py_ssize_t _i, PyObject *_value"_s, - intT()}, + intT}, {u"__getslice__"_s, u"PyObject *self, Py_ssize_t _i1, Py_ssize_t _i2"_s, u"PyObject*"_s}, {u"__setslice__"_s, u"PyObject *self, Py_ssize_t _i1, Py_ssize_t _i2, PyObject *_value"_s, - intT()}, + intT}, {u"__contains__"_s, u"PyObject *self, PyObject *_value"_s, - intT()}, + intT}, {u"__concat__"_s, u"PyObject *self, PyObject *_other"_s, u"PyObject*"_s} @@ -579,13 +621,15 @@ void CppGenerator::generateClass(TextStream &s, const GeneratorContext &classCon writeVirtualMethodNative(s, func, maxOverrides++); } - if (!avoidProtectedHack() || !metaClass->hasPrivateDestructor()) { - if (usePySideExtensions() && isQObject(metaClass)) - writeMetaObjectMethod(s, classContext); + if (shouldGenerateMetaObjectFunctions(metaClass)) + writeMetaObjectMethod(s, classContext); + if (!avoidProtectedHack() || !metaClass->hasPrivateDestructor()) writeDestructorNative(s, classContext); - } } + for (const auto &f : metaClass->userAddedPythonOverrides()) + writeUserAddedPythonOverride(s, f); + StringStream smd(TextStream::Language::Cpp); StringStream md(TextStream::Language::Cpp); StringStream signatureStream(TextStream::Language::Cpp); @@ -932,17 +976,24 @@ void CppGenerator::writeVirtualMethodCppCall(TextStream &s, } // Determine the return statement (void or a result value). -QString CppGenerator::virtualMethodReturn(TextStream &s, const ApiExtractorResult &api, - const AbstractMetaFunctionCPtr &func, - const FunctionModificationList &functionModifications) + +CppGenerator::VirtualMethodReturn + CppGenerator::virtualMethodReturn(const ApiExtractorResult &api, + const AbstractMetaFunctionCPtr &func, + const FunctionModificationList &functionModifications) { - if (func->isVoid()) - return u"return;"_s; + VirtualMethodReturn result; + if (func->isVoid()) { + result.statement = "return;"_L1; + return result; + } + + result.statement = "return "_L1; const AbstractMetaType &returnType = func->type(); for (const FunctionModification &mod : functionModifications) { for (const ArgumentModification &argMod : mod.argument_mods()) { if (argMod.index() == 0 && !argMod.replacedDefaultExpression().isEmpty()) { - static const QRegularExpression regex(QStringLiteral("%(\\d+)")); + static const QRegularExpression regex("%(\\d+)"_L1); Q_ASSERT(regex.isValid()); QString expr = argMod.replacedDefaultExpression(); for (int offset = 0; ; ) { @@ -958,8 +1009,8 @@ QString CppGenerator::virtualMethodReturn(TextStream &s, const ApiExtractorResul offset = match.capturedStart(1); } DefaultValue defaultReturnExpr(DefaultValue::Custom, expr); - return u"return "_s + defaultReturnExpr.returnValue() - + u';'; + result.statement += defaultReturnExpr.returnValue() + u';'; + return result; } } } @@ -971,16 +1022,13 @@ QString CppGenerator::virtualMethodReturn(TextStream &s, const ApiExtractorResul errorMsg = msgCouldNotFindMinimalConstructor(errorMsg, func->type().cppSignature(), errorMessage); - qCWarning(lcShiboken).noquote().nospace() << errorMsg; - s << "\n#error " << errorMsg << '\n'; - } - if (returnType.referenceType() == LValueReference) { - s << "static " << returnType.typeEntry()->qualifiedCppName() - << " result;\n"; - return u"return result;"_s; + throw Exception(errorMsg); } - return u"return "_s + defaultReturnExpr->returnValue() - + u';'; + + result.needsReference = returnType.referenceType() == LValueReference; + result.statement += (result.needsReference + ? virtualMethodStaticReturnVar : defaultReturnExpr->returnValue()) + u';'; + return result; } // Create an argument for Py_BuildValue() when writing virtual methods. @@ -1073,13 +1121,42 @@ static bool isArgumentNotRemoved(const AbstractMetaArgument &a) // PyObject_Vectorcall(): since 3.9 static const char vectorCallCondition[] = - "#if !defined(PYPY_VERSION) && !defined(Py_LIMITED_API) && PY_VERSION_HEX >= 0x03090000\n"; + "#if !defined(PYPY_VERSION) && !defined(Py_LIMITED_API)\n"; // PyObject_CallNoArgs(): since 3.9, stable API since 3.10 static const char noArgsCallCondition[] = - "#if !defined(PYPY_VERSION) && ((defined(Py_LIMITED_API) && Py_LIMITED_API >= 0x030A0000) || (!defined(Py_LIMITED_API) && PY_VERSION_HEX >= 0x03090000))\n"; + "#if !defined(PYPY_VERSION) && ((defined(Py_LIMITED_API) && Py_LIMITED_API >= 0x030A0000) || !defined(Py_LIMITED_API))\n"; static const char inverseNoArgsCallCondition[] = - "#if defined(PYPY_VERSION) || (defined(Py_LIMITED_API) && Py_LIMITED_API < 0x030A0000) || (!defined(Py_LIMITED_API) && PY_VERSION_HEX < 0x03090000)\n"; + "#if defined(PYPY_VERSION) || (defined(Py_LIMITED_API) && Py_LIMITED_API < 0x030A0000)\n"; + +static inline void writeVirtualMethodStaticReturnVar(TextStream &s, const AbstractMetaFunctionCPtr &func) +{ + s << "static " << func->type().typeEntry()->qualifiedCppName() << ' ' + << virtualMethodStaticReturnVar << ";\n"; +} + +static void writeFuncNameVar(TextStream &s, const AbstractMetaFunctionCPtr &func, + const QString &funcName) +{ + // PYSIDE-1019: Add info about properties + int propFlag = 0; + if (func->isPropertyReader()) + propFlag |= 1; + if (func->isPropertyWriter()) + propFlag |= 2; + if (propFlag && func->isStatic()) + propFlag |= 4; + QString propStr; + if (propFlag != 90) + propStr = QString::number(propFlag) + u':'; + + if (propFlag != 0) + s << "// This method belongs to a property.\n"; + s << "static const char *funcName = \""; + if (propFlag != 0) + s << propFlag << ':'; + s << funcName << "\";\n"; +} void CppGenerator::writeVirtualMethodNative(TextStream &s, const AbstractMetaFunctionCPtr &func, @@ -1094,13 +1171,16 @@ void CppGenerator::writeVirtualMethodNative(TextStream &s, Generator::OriginalTypeDescription) << "\n{\n" << indent; - const QString returnStatement = virtualMethodReturn(s, api(), func, - func->modifications()); + const auto returnStatement = virtualMethodReturn(api(), func, + func->modifications()); + + if (returnStatement.needsReference) + writeVirtualMethodStaticReturnVar(s, func); const bool isAbstract = func->isAbstract(); if (isAbstract && func->isModifiedRemoved()) { qCWarning(lcShiboken, "%s", qPrintable(msgPureVirtualFunctionRemoved(func.get()))); - s << returnStatement << '\n' << outdent << "}\n\n"; + s << returnStatement.statement << '\n' << outdent << "}\n\n"; return; } @@ -1129,7 +1209,7 @@ void CppGenerator::writeVirtualMethodNative(TextStream &s, s << "if (m_PyMethodCache[" << cacheIndex << "])" << (multi_line ? " {\n" : "\n") << indent; writeVirtualMethodCppCall(s, func, funcName, snips, lastArg, retType, - returnStatement, false); + returnStatement.statement, false); s << outdent; if (multi_line) s << "}\n"; @@ -1138,33 +1218,32 @@ void CppGenerator::writeVirtualMethodNative(TextStream &s, // Get out of virtual method call if someone already threw an error. s << "if (" << shibokenErrorsOccurred << ")\n" << indent - << returnStatement << '\n' << outdent; - - // PYSIDE-1019: Add info about properties - int propFlag = 0; - if (func->isPropertyReader()) - propFlag |= 1; - if (func->isPropertyWriter()) - propFlag |= 2; - if (propFlag && func->isStatic()) - propFlag |= 4; - QString propStr; - if (propFlag) - propStr = QString::number(propFlag) + u':'; + << returnStatement.statement << '\n' << outdent; s << "static PyObject *nameCache[2] = {};\n"; - if (propFlag) - s << "// This method belongs to a property.\n"; - s << "static const char *funcName = \"" << propStr << funcName << "\";\n" - << "Shiboken::AutoDecRef " << PYTHON_OVERRIDE_VAR + writeFuncNameVar(s, func, funcName); + s << "Shiboken::AutoDecRef " << PYTHON_OVERRIDE_VAR << "(Shiboken::BindingManager::instance().getOverride(this, nameCache, funcName));\n" << "if (" << PYTHON_OVERRIDE_VAR << ".isNull()) {\n" << indent; if (useOverrideCaching(func->ownerClass())) s << "m_PyMethodCache[" << cacheIndex << "] = true;\n"; writeVirtualMethodCppCall(s, func, funcName, snips, lastArg, retType, - returnStatement, true); + returnStatement.statement, true); s << outdent << "}\n\n"; //WS + if (!snips.isEmpty()) { + writeCodeSnips(s, snips, TypeSystem::CodeSnipPositionPyOverride, + TypeSystem::ShellCode, func, false, lastArg); + } + + writeVirtualMethodPythonOverride(s, func, snips, returnStatement); +} + +void CppGenerator::writeVirtualMethodPythonOverride(TextStream &s, + const AbstractMetaFunctionCPtr &func, + const CodeSnipList &snips, + const VirtualMethodReturn &returnStatement) const +{ writeConversionRule(s, func, TypeSystem::TargetLangCode, false); bool invalidateReturn = false; @@ -1187,7 +1266,7 @@ void CppGenerator::writeVirtualMethodNative(TextStream &s, auto arguments = func->arguments(); auto removedEnd = std::stable_partition(arguments.begin(), arguments.end(), isArgumentNotRemoved); - if (isAbstract) { // Base function is not called, indicate unused arguments. + if (func->isAbstract()) { // Base function is not called, indicate unused arguments. for (auto it = removedEnd; it != arguments.end(); ++it) s << sbkUnusedVariableCast(it->name()); } @@ -1259,7 +1338,7 @@ void CppGenerator::writeVirtualMethodNative(TextStream &s, s << "if (" << PYTHON_RETURN_VAR << ".isNull()) {\n" << indent << "// An error happened in python code!\n" << "Shiboken::Errors::storeErrorOrPrint();\n" - << returnStatement << "\n" << outdent + << returnStatement.statement << "\n" << outdent << "}\n"; if (invalidateReturn) { @@ -1271,7 +1350,7 @@ void CppGenerator::writeVirtualMethodNative(TextStream &s, if (!func->isVoid()) { - if (func->modifiedTypeName() != cPyObjectT()) { + if (func->modifiedTypeName() != cPyObjectT) { s << "// Check return type\n"; @@ -1286,7 +1365,7 @@ void CppGenerator::writeVirtualMethodNative(TextStream &s, << func->ownerClass()->name() << "\", funcName, " << getVirtualFunctionReturnTypeName(func) << ", " << "Py_TYPE(" << PYTHON_RETURN_VAR << ")->tp_name);\n" - << returnStatement << '\n' << outdent + << returnStatement.statement << '\n' << outdent << "}\n"; } else { @@ -1308,7 +1387,7 @@ void CppGenerator::writeVirtualMethodNative(TextStream &s, << func->ownerClass()->name() << "\", funcName, " << getVirtualFunctionReturnTypeName(func) << ", " << "Py_TYPE(" << PYTHON_RETURN_VAR << ")->tp_name);\n" - << returnStatement << '\n' << outdent + << returnStatement.statement << '\n' << outdent << "}\n"; } @@ -1345,6 +1424,7 @@ void CppGenerator::writeVirtualMethodNative(TextStream &s, if (!func->isVoid()) { s << "return "; + TypeEntryCPtr retType = func->type().typeEntry(); if (avoidProtectedHack() && retType->isEnum()) { auto metaEnum = api().findAbstractMetaEnum(retType); bool isProtectedEnum = metaEnum.has_value() && metaEnum->isProtected(); @@ -1365,6 +1445,28 @@ void CppGenerator::writeVirtualMethodNative(TextStream &s, s << outdent << "}\n\n"; } +void CppGenerator::writeUserAddedPythonOverride(TextStream &s, + const AbstractMetaFunctionCPtr &func) const +{ + TypeEntryCPtr retType = func->type().typeEntry(); + const QString funcName = func->isOperatorOverload() + ? pythonOperatorFunctionName(func) : func->definitionNames().constFirst(); + + const CodeSnipList snips = func->hasInjectedCode() + ? func->injectedCodeSnips() : CodeSnipList(); + + QString prefix = wrapperName(func->ownerClass()) + u"::"_s; + s << '\n' << functionSignature(func, prefix, QString(), Generator::SkipDefaultValues | + Generator::OriginalTypeDescription) + << "\n{\n" << indent << sbkUnusedVariableCast("gil"); + + writeFuncNameVar(s, func, funcName); + + const auto returnStatement = virtualMethodReturn(api(), func, + func->modifications()); + writeVirtualMethodPythonOverride(s, func, snips, returnStatement); +} + void CppGenerator::writeMetaObjectMethod(TextStream &s, const GeneratorContext &classContext) const { @@ -1537,7 +1639,7 @@ void CppGenerator::writeConverterFunctions(TextStream &s, const AbstractMetaClas << "if (pyOut) {\n" << indent << "Py_INCREF(pyOut);\nreturn pyOut;\n" << outdent << "}\n" - << "bool changedTypeName = false;\n" + << "bool exactType = false;\n" << "auto *tCppIn = reinterpret_cast(cppIn); const char *typeName = )"; @@ -1547,15 +1649,11 @@ const char *typeName = )"; else c << nameFunc << "(tCppIn);\n"; c << R"(auto *sbkType = Shiboken::ObjectType::typeForTypeName(typeName); -if (sbkType != nullptr && Shiboken::ObjectType::hasSpecialCastFunction(sbkType)) { - typeName = Shiboken::typeNameOf(typeid(*tCppIn).name()); - changedTypeName = true; -} +if (sbkType != nullptr && Shiboken::ObjectType::hasSpecialCastFunction(sbkType)) + exactType = true; )" << "PyObject *result = Shiboken::Object::newObject(" << cpythonType - << R"(, const_cast(cppIn), false, /* exactType */ changedTypeName, typeName); -if (changedTypeName) - delete [] typeName; + << R"(, const_cast(cppIn), false, exactType, typeName); return result;)"; } std::swap(targetTypeName, sourceTypeName); @@ -1585,7 +1683,7 @@ return result;)"; c << "auto *source = reinterpret_cast(cppIn);\n"; } c << "return Shiboken::Object::newObject(" << cpythonType - << ", new ::" << classContext.effectiveClassName() << '(' + << ", new " << globalScopePrefix(classContext) << classContext.effectiveClassName() << '(' << (isUniquePointer ? "std::move(*source)" : "*source") << "), true, true);"; writeCppToPythonFunction(s, c.toString(), sourceTypeName, targetTypeName); @@ -1595,7 +1693,7 @@ return result;)"; s << "// Python to C++ copy conversion.\n"; sourceTypeName = metaClass->name(); - targetTypeName = sourceTypeName + QStringLiteral("_COPY"); + targetTypeName = sourceTypeName + "_COPY"_L1; c.clear(); QString pyInVariable = u"pyIn"_s; @@ -1739,9 +1837,8 @@ void CppGenerator::writeConverterRegister(TextStream &s, const AbstractMetaClass auto writeConversions = [&s](const QString &signature) { - s << "Shiboken::Conversions::registerConverterName(converter, \"" << signature << "\");\n" - << "Shiboken::Conversions::registerConverterName(converter, \"" << signature << "*\");\n" - << "Shiboken::Conversions::registerConverterName(converter, \"" << signature << "&\");\n"; + s << registerConverterName(signature) << registerConverterName(signature + u'*') + << registerConverterName(signature + u'&'); }; auto writeConversionsForType = [writeConversions](const QString &fullTypeName) @@ -1773,7 +1870,7 @@ void CppGenerator::writeConverterRegister(TextStream &s, const AbstractMetaClass writeConversionsForType(smartPointerType); } - s << "Shiboken::Conversions::registerConverterName(converter, typeid(::"; + s << "Shiboken::Conversions::registerConverterName(converter, typeid(" << m_gsp; QString qualifiedCppNameInvocation; if (!classContext.forSmartPointer()) qualifiedCppNameInvocation = metaClass->qualifiedCppName(); @@ -1783,7 +1880,7 @@ void CppGenerator::writeConverterRegister(TextStream &s, const AbstractMetaClass s << qualifiedCppNameInvocation << ").name());\n"; if (classContext.useWrapper()) { - s << "Shiboken::Conversions::registerConverterName(converter, typeid(::" + s << "Shiboken::Conversions::registerConverterName(converter, typeid(" << classContext.wrapperName() << ").name());\n"; } @@ -1884,7 +1981,9 @@ void CppGenerator::writeMethodWrapperPreamble(TextStream &s, if (rfunc->isConstructor()) { // Check if the right constructor was called. if (!ownerClass->hasPrivateDestructor()) { - s << "if (Shiboken::Object::isUserType(self) && !Shiboken::ObjectType::canCallConstructor(self->ob_type, Shiboken::SbkType< ::"; + s << "if (Shiboken::Object::isUserType(self) && " + << "!Shiboken::ObjectType::canCallConstructor(self->ob_type, Shiboken::SbkType< " + << m_gsp; QString qualifiedCppName; if (!context.forSmartPointer()) qualifiedCppName = ownerClass->qualifiedCppName(); @@ -1894,7 +1993,7 @@ void CppGenerator::writeMethodWrapperPreamble(TextStream &s, s << qualifiedCppName << " >()))\n" << indent << errorReturn << outdent << '\n'; } // Declare pointer for the underlying C++ object. - s << "::" << context.effectiveClassName() << " *cptr{};\n"; + s << globalScopePrefix(context) << context.effectiveClassName() << " *cptr{};\n"; initPythonArguments = maxArgs > 0; @@ -1957,8 +2056,8 @@ void CppGenerator::writeConstructorWrapper(TextStream &s, const OverloadData &ov s << cpythonFunctionName(rfunc) << "(PyObject *self, PyObject *args, PyObject *kwds)\n{\n" << indent; if (overloadData.maxArgs() == 0 || metaClass->isAbstract()) - s << sbkUnusedVariableCast(u"args"_s); - s << sbkUnusedVariableCast(u"kwds"_s); + s << sbkUnusedVariableCast("args"); + s << sbkUnusedVariableCast("kwds"); const bool needsMetaObject = usePySideExtensions() && isQObject(metaClass); if (needsMetaObject) @@ -1975,11 +2074,11 @@ void CppGenerator::writeConstructorWrapper(TextStream &s, const OverloadData &ov if (metaClass->isAbstract()) { // C++ Wrapper disabled: Abstract C++ class cannot be instantiated. if (metaClass->typeEntry()->typeFlags().testFlag(ComplexTypeEntry::DisableWrapper)) { - s << sbkUnusedVariableCast(u"sbkSelf"_s) - << sbkUnusedVariableCast(u"type"_s) - << sbkUnusedVariableCast(u"myType"_s); + s << sbkUnusedVariableCast("sbkSelf") + << sbkUnusedVariableCast("type") + << sbkUnusedVariableCast("myType"); if (needsMetaObject) - s << sbkUnusedVariableCast(u"metaObject"_s); + s << sbkUnusedVariableCast("metaObject"); s << "Shiboken::Errors::setInstantiateAbstractClassDisabledWrapper(\"" << metaClass->qualifiedCppName() << "\");\n" << errorReturn << outdent << "}\n\n"; @@ -2026,8 +2125,8 @@ void CppGenerator::writeConstructorWrapper(TextStream &s, const OverloadData &ov const QString typeName = classContext.forSmartPointer() ? classContext.preciseType().cppSignature() : metaClass->qualifiedCppName(); s << "if (" << shibokenErrorsOccurred - << " || !Shiboken::Object::setCppPointer(sbkSelf, Shiboken::SbkType< ::" - << typeName << " >(), cptr)) {\n" + << " || !Shiboken::Object::setCppPointer(sbkSelf, Shiboken::SbkType< " + << globalScopePrefix(classContext) << typeName << " >(), cptr)) {\n" << indent << "delete cptr;\n" << errorReturn << outdent << "}\n"; if (overloadData.maxArgs() > 0) @@ -2117,9 +2216,9 @@ void CppGenerator::writeMethodWrapper(TextStream &s, const OverloadData &overloa } s << ")\n{\n" << indent; if (rfunc->ownerClass() == nullptr || overloadData.hasStaticFunction()) - s << sbkUnusedVariableCast(u"self"); + s << sbkUnusedVariableCast(PYTHON_SELF_VAR); if (hasKwdArgs) - s << sbkUnusedVariableCast(u"kwds"); + s << sbkUnusedVariableCast("kwds"); writeMethodWrapperPreamble(s, overloadData, classContext); @@ -2190,7 +2289,7 @@ void CppGenerator::writeArgumentsInitializer(TextStream &s, const OverloadData & ErrorReturn errorReturn) { const auto rfunc = overloadData.referenceFunction(); - s << "PyTuple_GET_SIZE(args);\n" << sbkUnusedVariableCast(u"numArgs"_s); + s << "PyTuple_GET_SIZE(args);\n" << sbkUnusedVariableCast("numArgs"); int minArgs = overloadData.minArgs(); int maxArgs = overloadData.maxArgs(); @@ -2273,10 +2372,9 @@ void CppGenerator::writeCppSelfConversion(TextStream &s, const GeneratorContext return; } - static const QString pythonSelfVar = u"self"_s; if (useWrapperClass) s << "static_cast<" << className << " *>("; - s << cpythonWrapperCPtr(context.metaClass(), pythonSelfVar); + s << cpythonWrapperCPtr(context.metaClass(), PYTHON_SELF_VAR); if (useWrapperClass) s << ')'; } @@ -2311,7 +2409,7 @@ void CppGenerator::writeCppSelfDefinition(TextStream &s, const QString className = useWrapperClass ? context.wrapperName() : getFullTypeName(metaClass); - writeInvalidPyObjectCheck(s, u"self"_s, errorReturn); + writeInvalidPyObjectCheck(s, PYTHON_SELF_VAR, errorReturn); if (flags.testFlag(CppSelfAsReference)) { writeCppSelfVarDef(s, flags); @@ -2557,12 +2655,12 @@ static inline QString arrayHandleType(const AbstractMetaTypeList &nestedArrayTyp { switch (nestedArrayTypes.size()) { case 1: - return QStringLiteral("Shiboken::Conversions::ArrayHandle<") + return "Shiboken::Conversions::ArrayHandle<"_L1 + nestedArrayTypes.constLast().minimalSignature() + u'>'; case 2: - return QStringLiteral("Shiboken::Conversions::Array2Handle<") + return "Shiboken::Conversions::Array2Handle<"_L1 + nestedArrayTypes.constLast().minimalSignature() - + QStringLiteral(", ") + + ", "_L1 + QString::number(nestedArrayTypes.constFirst().arrayElementCount()) + u'>'; } @@ -2887,7 +2985,7 @@ void CppGenerator::writeOverloadedFunctionDecisorEngine(TextStream &s, int sequenceArgCount = 0; while (od && !od->argType().isVarargs()) { const bool typeReplacedByPyObject = od->isTypeModified() - && od->modifiedArgType().name() == cPyObjectT(); + && od->modifiedArgType().name() == cPyObjectT; if (!typeReplacedByPyObject) { if (usePyArgs) pyArgName = pythonArgsAt(od->argPos()); @@ -3047,10 +3145,9 @@ void CppGenerator::writeSingleFunctionCall(TextStream &s, continue; auto argType = getArgumentType(func, argIdx); int argPos = argIdx - removedArgs; - QString argName = CPP_ARG(argPos); QString pyArgName = usePyArgs ? pythonArgsAt(argPos) : PYTHON_ARG; indirections[argIdx] = - writeArgumentConversion(s, argType, argName, pyArgName, errorReturn, + writeArgumentConversion(s, argType, CPP_ARG_N(argPos), pyArgName, errorReturn, func->implementingClass(), arg.defaultValueExpression(), func->isUserAdded()); } @@ -3155,10 +3252,10 @@ void CppGenerator::writeCppToPythonFunction(TextStream &s, QString CppGenerator::containerNativeToTargetTypeName(const ContainerTypeEntryCPtr &type) { QString result = type->targetLangApiName(); - if (result != cPyObjectT()) { + if (result != cPyObjectT) { result = containerCpythonBaseName(type); - if (result == cPySequenceT()) - result = cPyListT(); + if (result == cPySequenceT) + result = cPyListT; } return result; } @@ -3217,7 +3314,7 @@ void CppGenerator::writeIsPythonConvertibleToCppFunction(TextStream &s, << "return Shiboken::Conversions::nonePythonToCppNullPtr;\n" << outdent; } else { if (!condition.contains(u"pyIn")) - s << sbkUnusedVariableCast(u"pyIn"); + s << sbkUnusedVariableCast("pyIn"); } s << "if (" << condition << ")\n" << indent << "return " << pythonToCppFuncName << ";\n" << outdent @@ -3474,7 +3571,7 @@ QString CppGenerator::argumentNameFromIndex(const ApiExtractorResult &api, { switch (argIndex) { case -1: - return u"self"_s; + return PYTHON_SELF_VAR; case 0: return PYTHON_RETURN_VAR; case 1: { // Single argument? @@ -3624,7 +3721,7 @@ void CppGenerator::writeMethodCall(TextStream &s, const AbstractMetaFunctionCPtr const int idx = arg.argumentIndex() - removedArgs; const auto deRef = argumentIndirections.at(i); QString argName = AbstractMetaType::dereferencePrefix(deRef) - + CPP_ARG(idx); + + CPP_ARG_N(idx); userArgs.append(argName); } } @@ -3703,7 +3800,7 @@ void CppGenerator::writeMethodCall(TextStream &s, const AbstractMetaFunctionCPtr Q_ASSERT(owner == context.metaClass()); if (func->functionType() == AbstractMetaFunction::CopyConstructorFunction && maxArgs == 1) { - mc << "new ::" << context.effectiveClassName() + mc << "new " << globalScopePrefix(context) << context.effectiveClassName() << "(*" << CPP_ARG0 << ')'; } else { const QString ctorCall = context.effectiveClassName() + u'(' @@ -3711,13 +3808,13 @@ void CppGenerator::writeMethodCall(TextStream &s, const AbstractMetaFunctionCPtr if (usePySideExtensions() && isQObject(owner)) { s << "void *addr = PySide::nextQObjectMemoryAddr();\n"; uva << "if (addr != nullptr) {\n" << indent - << "cptr = new (addr) ::" << ctorCall << ";\n" - << "PySide::setNextQObjectMemoryAddr(nullptr);\n" << outdent + << "cptr = new (addr) " << globalScopePrefix(context) << ctorCall + << ";\nPySide::setNextQObjectMemoryAddr(nullptr);\n" << outdent << "} else {\n" << indent - << "cptr = new ::" << ctorCall << ";\n" + << "cptr = new " << globalScopePrefix(context) << ctorCall << ";\n" << outdent << "}\n"; } else { - mc << "new ::" << ctorCall; + mc << "new " << globalScopePrefix(context) << ctorCall; } } } else { @@ -3731,7 +3828,7 @@ void CppGenerator::writeMethodCall(TextStream &s, const AbstractMetaFunctionCPtr const bool hasWrapper = shouldGenerateCppWrapper(ownerClass); if (!avoidProtectedHack() || !func->isProtected() || !hasWrapper) { if (func->isStatic()) { - mc << "::" << methodCallClassName << "::"; + mc << m_gsp << methodCallClassName << "::"; } else { const QString cppSelfVar = CPP_SELF_VAR; const QString selfVarCast = func->ownerClass() == func->implementingClass() @@ -3740,7 +3837,7 @@ void CppGenerator::writeMethodCall(TextStream &s, const AbstractMetaFunctionCPtr + u" *>("_s + cppSelfVar + u')'; if (func->isConstant()) { if (avoidProtectedHack()) { - mc << "const_castcppWrapper().testFlag(AbstractMetaClass::CppProtectedHackWrapper)) { // PYSIDE-500: Need a special wrapper cast when inherited const QString selfWrapCast = ownerClass == func->implementingClass() @@ -3755,7 +3852,7 @@ void CppGenerator::writeMethodCall(TextStream &s, const AbstractMetaFunctionCPtr mc << " *>(" << selfVarCast << ")->"; } } else { - mc << "const_cast(" << selfVarCast << ")->"; } } else { @@ -3777,8 +3874,7 @@ void CppGenerator::writeMethodCall(TextStream &s, const AbstractMetaFunctionCPtr if (!func->isAbstract()) mc << (func->isProtected() ? wrapperName(func->ownerClass()) : - u"::"_s - + methodCallClassName) << "::"; + m_gsp + methodCallClassName) << "::"; mc << func->originalName() << "_protected"; } } else { @@ -4033,7 +4129,7 @@ void CppGenerator::writeSpecialCastFunction(TextStream &s, const AbstractMetaCla QString className = metaClass->qualifiedCppName(); s << "static void * " << cpythonSpecialCastFunctionName(metaClass) << "(void *obj, PyTypeObject *desiredType)\n{\n" << indent - << "auto me = reinterpret_cast< ::" << className << " *>(obj);\n"; + << "auto me = reinterpret_cast< " << m_gsp << className << " *>(obj);\n"; bool firstClass = true; const auto &allAncestors = metaClass->allTypeSystemAncestors(); for (const auto &baseClass : allAncestors) { @@ -4057,27 +4153,25 @@ void CppGenerator::writePrimitiveConverterInitialization(TextStream &s, << converter << " = Shiboken::Conversions::createConverter("; if (!type->hasTargetLangApiType()) s << "nullptr"; - else if (type->targetLangApiName() == cPyObjectT()) + else if (type->targetLangApiName() == cPyObjectT) s << "&PyBaseObject_Type"; else s << '&' << type->targetLangApiName() << "_Type"; QString typeName = fixedCppTypeName(type); s << ", " << cppToPythonFunctionName(typeName, typeName) << ");\n" - << "Shiboken::Conversions::registerConverterName(" << converter << ", \"" - << type->qualifiedCppName() << "\");\n"; + << registerConverterName(type->qualifiedCppName(), converter); writeCustomConverterRegister(s, customConversion, converter); } -static void registerEnumConverterScopes(TextStream &s, QString signature) +static void registerConverterInScopes(TextStream &s, QStringView signature, + QAnyStringView varName = converterVar) { while (true) { - s << "Shiboken::Conversions::registerConverterName(converter, \"" - << signature << "\");\n"; - const auto qualifierPos = signature.indexOf(u"::"); - if (qualifierPos != -1) - signature.remove(0, qualifierPos + 2); - else + s << registerConverterName(signature, varName); + const auto qualifierPos = signature.indexOf("::"_L1); + if (qualifierPos == -1) break; + signature = signature.sliced(qualifierPos + 2); } } @@ -4104,27 +4198,30 @@ void CppGenerator::writeEnumConverterInitialization(TextStream &s, const Abstrac s << "Shiboken::Enum::setTypeConverter(" << enumPythonVar << ", converter);\n"; - registerEnumConverterScopes(s, enumType->qualifiedCppName()); + registerConverterInScopes(s, enumType->qualifiedCppName()); if (auto flags = enumType->flags()) s << "// Register converter for flag '" << flags->qualifiedCppName() << "'.\n" - << "Shiboken::Conversions::registerConverterName(converter, \"" - << flags->name() << "\");\n"; + << registerConverterName(flags->name()) // QMetaType + << registerConverterName(flags->originalName()); // Signals with flags + s << outdent << "}\n"; } QString CppGenerator::writeContainerConverterInitialization(TextStream &s, - const AbstractMetaType &type) + const AbstractMetaType &type, + const ApiExtractorResult &api) { - QByteArray cppSignature = QMetaObject::normalizedSignature(type.cppSignature().toUtf8()); + const auto cppSignature = + QString::fromUtf8(QMetaObject::normalizedSignature(type.cppSignature().toUtf8())); s << "// Register converter for type '" << cppSignature << "'.\n"; - QString converter = converterObject(type); + const QString converter = converterObject(type); s << converter << " = Shiboken::Conversions::createConverter("; Q_ASSERT(type.typeEntry()->isContainer()); const auto typeEntry = std::static_pointer_cast(type.typeEntry()); const QString targetTypeName = containerNativeToTargetTypeName(typeEntry); - if (targetTypeName == cPyObjectT()) { + if (targetTypeName == cPyObjectT) { s << "&PyBaseObject_Type"; } else { s << '&' << targetTypeName << "_Type"; @@ -4133,24 +4230,39 @@ QString CppGenerator::writeContainerConverterInitialization(TextStream &s, const QString typeName = fixedCppTypeName(type); s << ", " << cppToPythonFunctionName(typeName, targetTypeName) << ");\n"; + s << registerConverterName(cppSignature, converter); + if (usePySideExtensions() && cppSignature.startsWith("const "_L1) + && cppSignature.endsWith(u'&')) { + auto underlyingType = QStringView{cppSignature}.sliced(6, cppSignature.size() - 7); + s << registerConverterName(underlyingType, converter); + } + for (const auto &conv : typeEntry->customConversion()->targetToNativeConversions()) { const QString &sourceTypeName = conv.sourceTypeName(); QString toCpp = pythonToCppFunctionName(sourceTypeName, typeName); QString isConv = convertibleToCppFunctionName(sourceTypeName, typeName); - s << "Shiboken::Conversions::registerConverterName(" << converter - << ", \"" << cppSignature << "\");\n"; - if (usePySideExtensions() && cppSignature.startsWith("const ") - && cppSignature.endsWith("&")) { - cppSignature.chop(1); - cppSignature.remove(0, sizeof("const ") / sizeof(char) - 1); - s << "Shiboken::Conversions::registerConverterName(" << converter - << ", \"" << cppSignature << "\");\n"; - } writeAddPythonToCppConversion(s, converter, toCpp, isConv); } + + auto typedefItPair = api.typedefTargetToName().equal_range(type.cppSignature()); + if (typedefItPair.first != typedefItPair.second) { + auto *typeDb = TypeDatabase::instance(); + s << "// Register converters for type aliases of " << cppSignature << "'.\n"; + for (auto it = typedefItPair.first; it != typedefItPair.second; ++it) { + if (!typeDb->findType(it.value())) + s << registerConverterName(it.value(), converter); + } + } + return converter; } +QString CppGenerator::typeInitStruct(const TypeEntryCPtr &te) +{ + return cppApiVariableName(te->targetLangPackage()) + u'[' + + getTypeIndexVariableName(te) + u']'; +} + void CppGenerator::writeExtendedConverterInitialization(TextStream &s, const TypeEntryCPtr &externalType, const AbstractMetaClassCList &conversions) @@ -4158,13 +4270,13 @@ void CppGenerator::writeExtendedConverterInitialization(TextStream &s, s << "// Extended implicit conversions for " << externalType->qualifiedTargetLangName() << ".\n"; for (const auto &sourceClass : conversions) { - const QString converterVar = cppApiVariableName(externalType->targetLangPackage()) + u'[' - + getTypeIndexVariableName(externalType) + u']'; QString sourceTypeName = fixedCppTypeName(sourceClass->typeEntry()); QString targetTypeName = fixedCppTypeName(externalType); QString toCpp = pythonToCppFunctionName(sourceTypeName, targetTypeName); QString isConv = convertibleToCppFunctionName(sourceTypeName, targetTypeName); - writeAddPythonToCppConversion(s, converterVar, toCpp, isConv); + if (!externalType->isPrimitive()) + s << cpythonTypeNameExt(externalType) << ";\n"; + writeAddPythonToCppConversion(s, typeInitStruct(externalType), toCpp, isConv); } } @@ -4221,11 +4333,11 @@ bool CppGenerator::shouldGenerateGetSetList(const AbstractMetaClassCPtr &metaCla struct pyTypeSlotEntry { - explicit pyTypeSlotEntry(QStringView name, QStringView function) : + explicit pyTypeSlotEntry(QAnyStringView name, QAnyStringView function) : m_name(name), m_function(function) {} - QStringView m_name; - QStringView m_function; + QAnyStringView m_name; + QAnyStringView m_function; }; TextStream &operator<<(TextStream &str, const pyTypeSlotEntry &e) @@ -4247,7 +4359,6 @@ void CppGenerator::writeClassDefinition(TextStream &s, QString tp_hash; QString tp_call; const QString className = chopType(cpythonTypeName(metaClass)); - QString baseClassName; AbstractMetaFunctionCList ctors; const auto &allCtors = metaClass->queryFunctions(FunctionQueryOption::AnyConstructor); for (const auto &f : allCtors) { @@ -4257,9 +4368,6 @@ void CppGenerator::writeClassDefinition(TextStream &s, } } - if (!metaClass->baseClass()) - baseClassName = u"SbkObject_TypeF()"_s; - bool onlyPrivCtor = !metaClass->hasNonPrivateConstructor(); const bool isQApp = usePySideExtensions() @@ -4353,7 +4461,7 @@ void CppGenerator::writeClassDefinition(TextStream &s, if (hasHashFunction(metaClass)) tp_hash = u'&' + cpythonBaseName(metaClass) + u"_HashFunc"_s; - const auto callOp = metaClass->findFunction(u"operator()"); + const auto callOp = metaClass->findFunction("operator()"); if (callOp && !callOp->isModifiedRemoved()) tp_call = u'&' + cpythonFunctionName(callOp); @@ -4364,22 +4472,22 @@ void CppGenerator::writeClassDefinition(TextStream &s, << "{\n" << indent << "return " << typePtr << ";\n" << outdent << "}\n\nstatic PyType_Slot " << className << "_slots[] = {\n" << indent << "{Py_tp_base, nullptr}, // inserted by introduceWrapperType\n" - << pyTypeSlotEntry(u"Py_tp_dealloc", tp_dealloc) - << pyTypeSlotEntry(u"Py_tp_repr", m_tpFuncs.value(REPR_FUNCTION)) - << pyTypeSlotEntry(u"Py_tp_hash", tp_hash) - << pyTypeSlotEntry(u"Py_tp_call", tp_call) - << pyTypeSlotEntry(u"Py_tp_str", m_tpFuncs.value(u"__str__"_s)) - << pyTypeSlotEntry(u"Py_tp_getattro", tp_getattro) - << pyTypeSlotEntry(u"Py_tp_setattro", tp_setattro) - << pyTypeSlotEntry(u"Py_tp_traverse", className + u"_traverse"_s) - << pyTypeSlotEntry(u"Py_tp_clear", className + u"_clear"_s) - << pyTypeSlotEntry(u"Py_tp_richcompare", tp_richcompare) - << pyTypeSlotEntry(u"Py_tp_iter", m_tpFuncs.value(u"__iter__"_s)) - << pyTypeSlotEntry(u"Py_tp_iternext", m_tpFuncs.value(u"__next__"_s)) - << pyTypeSlotEntry(u"Py_tp_methods", className + u"_methods"_s) - << pyTypeSlotEntry(u"Py_tp_getset", tp_getset) - << pyTypeSlotEntry(u"Py_tp_init", tp_init) - << pyTypeSlotEntry(u"Py_tp_new", tp_new); + << pyTypeSlotEntry("Py_tp_dealloc", tp_dealloc) + << pyTypeSlotEntry("Py_tp_repr", m_tpFuncs.value(REPR_FUNCTION)) + << pyTypeSlotEntry("Py_tp_hash", tp_hash) + << pyTypeSlotEntry("Py_tp_call", tp_call) + << pyTypeSlotEntry("Py_tp_str", m_tpFuncs.value(u"__str__"_s)) + << pyTypeSlotEntry("Py_tp_getattro", tp_getattro) + << pyTypeSlotEntry("Py_tp_setattro", tp_setattro) + << pyTypeSlotEntry("Py_tp_traverse", className + u"_traverse"_s) + << pyTypeSlotEntry("Py_tp_clear", className + u"_clear"_s) + << pyTypeSlotEntry("Py_tp_richcompare", tp_richcompare) + << pyTypeSlotEntry("Py_tp_iter", m_tpFuncs.value(u"__iter__"_s)) + << pyTypeSlotEntry("Py_tp_iternext", m_tpFuncs.value(u"__next__"_s)) + << pyTypeSlotEntry("Py_tp_methods", className + u"_methods"_s) + << pyTypeSlotEntry("Py_tp_getset", tp_getset) + << pyTypeSlotEntry("Py_tp_init", tp_init) + << pyTypeSlotEntry("Py_tp_new", tp_new); if (supportsSequenceProtocol(metaClass)) { s << "// type supports sequence protocol\n"; writeTypeAsSequenceDefinition(s, metaClass); @@ -4592,7 +4700,9 @@ void CppGenerator::writeTpTraverseFunction(TextStream &s, const AbstractMetaClas QString baseName = cpythonBaseName(metaClass); s << "static int " << baseName << "_traverse(PyObject *self, visitproc visit, void *arg)\n{\n" << indent - << "return SbkObject_TypeF()->tp_traverse(self, visit, arg);\n" + << "auto traverseProc = " + << pyTypeGetSlot("traverseproc", sbkObjectTypeF, "Py_tp_traverse") << ";\n" + << "return traverseProc(self, visit, arg);\n" << outdent << "}\n"; } @@ -4600,7 +4710,9 @@ void CppGenerator::writeTpClearFunction(TextStream &s, const AbstractMetaClassCP { QString baseName = cpythonBaseName(metaClass); s << "static int " << baseName << "_clear(PyObject *self)\n{\n" << indent - << "return reinterpret_cast(SbkObject_TypeF())->tp_clear(self);\n" + << "auto clearProc = " + << pyTypeGetSlot("inquiry", sbkObjectTypeF, "Py_tp_clear") << ";\n" + << "return clearProc(self);\n" << outdent << "}\n"; } @@ -4736,7 +4848,7 @@ void CppGenerator::writeGetterFunction(TextStream &s, { writeGetterFunctionStart(s, cpythonGetterFunctionName(property, context.metaClass())); writeCppSelfDefinition(s, context); - const QString value = QStringLiteral("value"); + const QString value = "value"_L1; s << "auto " << value << " = " << CPP_SELF_VAR << "->" << property.read() << "();\n" << "auto *pyResult = "; writeToPythonConversion(s, property.type(), context.metaClass(), value); @@ -4984,6 +5096,17 @@ QList return result; } +QString CppGenerator::pythonSignature(const AbstractMetaType &type) const +{ + if (type.isSmartPointer() && !type.instantiations().isEmpty()) { + const auto ste = std::static_pointer_cast(type.typeEntry()); + const auto instantiationTe = type.instantiations().constFirst().typeEntry(); + if (auto opt = api().findSmartPointerInstantiation(ste, instantiationTe)) + return opt->specialized->typeEntry()->qualifiedTargetLangName(); + } + return type.pythonSignature(); +} + // Format the type signature of a function parameter QString CppGenerator::signatureParameter(const AbstractMetaArgument &arg) const { @@ -4995,17 +5118,22 @@ QString CppGenerator::signatureParameter(const AbstractMetaArgument &arg) const metaType = *viewOn; s << arg.name() << ':'; - QStringList signatures(metaType.pythonSignature()); + QStringList signatures(pythonSignature(metaType)); // Implicit conversions (C++): Check for converting constructors // "QColor(Qt::GlobalColor)" or conversion operators const AbstractMetaFunctionCList conversions = api().implicitConversions(metaType); for (const auto &f : conversions) { - if (f->isConstructor() && !f->arguments().isEmpty()) - signatures << f->arguments().constFirst().type().pythonSignature(); - else if (f->isConversionOperator()) + if (f->isConstructor() && !f->arguments().isEmpty()) { + // PYSIDE-2712: modified types from converting constructors are not always correct + // candidates if they are modified by the type system reference + if (!f->arguments().constFirst().isTypeModified()) { + signatures << pythonSignature(f->arguments().constFirst().type()); + } + } else if (f->isConversionOperator()) { signatures << f->ownerClass()->fullName(); + } } const qsizetype size = signatures.size(); @@ -5035,7 +5163,7 @@ void CppGenerator::writeSignatureInfo(TextStream &s, const OverloadData &overloa // PYSIDE-1328: `self`-ness cannot be computed in Python because there are mixed cases. // Toplevel functions like `PySide6.QtCore.QEnum` are always self-less. if (!(f->isStatic()) && f->ownerClass()) - args << u"self"_s; + args << PYTHON_SELF_VAR; const auto &arguments = f->arguments(); for (qsizetype i = 0, size = arguments.size(); i < size; ++i) { const auto n = i + 1; @@ -5062,7 +5190,7 @@ void CppGenerator::writeSignatureInfo(TextStream &s, const OverloadData &overloa QString returnType = f->pyiTypeReplaced(0); // pyi or modified type if (returnType.isEmpty() && !f->isVoid()) - returnType = f->type().pythonSignature(); + returnType = pythonSignature(f->type()); if (!returnType.isEmpty()) s << "->" << returnType; @@ -5090,7 +5218,7 @@ void CppGenerator::writeEnumsInitialization(TextStream &s, AbstractMetaEnumList etypeUsed |= writeEnumInitialization(s, cppEnum); } if (preambleWritten && !etypeUsed) - s << sbkUnusedVariableCast(u"EType"); + s << sbkUnusedVariableCast("EType"); } static qsizetype maxLineLength(const QStringList &list) @@ -5214,7 +5342,7 @@ bool CppGenerator::writeEnumInitialization(TextStream &s, const AbstractMetaEnum bool etypeUsed = false; - QString enumVarTypeObj = cpythonTypeNameExt(enumTypeEntry); + QString enumVarTypeObj = cpythonTypeNameExtSet(enumTypeEntry); if (!cppEnum.isAnonymous()) { int packageLevel = packageName().count(u'.') + 1; s << "EType = Shiboken::Enum::" @@ -5228,7 +5356,7 @@ bool CppGenerator::writeEnumInitialization(TextStream &s, const AbstractMetaEnum if (cppEnum.typeEntry()->flags()) { s << "// PYSIDE-1735: Mapping the flags class to the same enum class.\n" - << cpythonTypeNameExt(cppEnum.typeEntry()->flags()) << " =\n" + << cpythonTypeNameExtSet(cppEnum.typeEntry()->flags()) << " =\n" << indent << "EType;\n" << outdent; } writeEnumConverterInitialization(s, cppEnum); @@ -5263,7 +5391,7 @@ void CppGenerator::writeSignalInitialization(TextStream &s, const AbstractMetaCl } } - s << "PySide::Signal::registerSignals(pyType, &::" + s << "PySide::Signal::registerSignals(pyType, &" << m_gsp << metaClass->qualifiedCppName() << "::staticMetaObject);\n"; } @@ -5328,6 +5456,61 @@ QString CppGenerator::destructorClassName(const AbstractMetaClassCPtr &metaClass return metaClass->qualifiedCppName(); } +// Return the base type entries for introduceWrapperType() +static ComplexTypeEntryCList pyBaseTypeEntries(const AbstractMetaClassCPtr &metaClass) +{ + ComplexTypeEntryCList result; + if (metaClass->isNamespace()) { + if (auto extended = metaClass->extendedNamespace()) + result.append(extended->typeEntry()); + return result; + } + + const auto &baseClasses = metaClass->typeSystemBaseClasses(); + for (auto base : baseClasses) { + for (; base != nullptr; base = base->baseClass()) { // Find a type that is not disabled. + const auto ct = base->typeEntry()->codeGeneration(); + if (ct == TypeEntry::GenerateCode || ct == TypeEntry::GenerateForSubclass) + break; + } + result.append(base->typeEntry()); + } + return result; +} + +// Return the base type strings for introduceWrapperType() +QStringList CppGenerator::pyBaseTypes(const AbstractMetaClassCPtr &metaClass) +{ + const auto &baseEntries = pyBaseTypeEntries(metaClass); + QStringList result; + for (const auto &baseEntry : baseEntries) + result.append("reinterpret_cast("_L1 + cpythonTypeNameExt(baseEntry) + u')'); + if (result.isEmpty()) // no base classes -> SbkObjectType. + result.append(sbkObjectTypeF); + return result; +} + +void CppGenerator::writeInitInheritance(TextStream &s) const +{ + s << "static void " << initInheritanceFunction << "()\n{\n" << indent + << "auto &bm = Shiboken::BindingManager::instance();\n" + << sbkUnusedVariableCast("bm"); + for (const auto &cls : api().classes()){ + auto te = cls->typeEntry(); + if (shouldGenerate(te)) { + const auto &baseEntries = pyBaseTypeEntries(cls); + if (!baseEntries.isEmpty()) { + const QString childTypeInitStruct = typeInitStruct(cls->typeEntry()); + for (const auto &baseEntry : baseEntries) { + s << "bm.addClassInheritance(&" << typeInitStruct(baseEntry) << ",\n" + << Pad(' ', 23) << '&' << childTypeInitStruct << ");\n"; + } + } + } + } + s << outdent << "}\n\n"; +} + void CppGenerator::writeClassRegister(TextStream &s, const AbstractMetaClassCPtr &metaClass, const GeneratorContext &classContext, @@ -5343,23 +5526,26 @@ void CppGenerator::writeClassRegister(TextStream &s, // PYSIDE-510: Create a signatures string for the introspection feature. writeSignatureStrings(s, signatures, initFunctionName, "functions"); - s << "void init_" << initFunctionName; - s << "(PyObject *" << enclosingObjectVariable << ")\n{\n" << indent; + s << "PyTypeObject *init_" << initFunctionName + << "(PyObject *" << enclosingObjectVariable << ")\n{\n" << indent; + + const QString globalTypeVarExpr = !classContext.forSmartPointer() + ? cpythonTypeNameExtSet(classTypeEntry) + : cpythonTypeNameExtSet(classContext.preciseType()); + s << "if (" << globalTypeVarExpr << " != nullptr)\n" << indent + << "return " << globalTypeVarExpr << ";\n\n" << outdent; // Multiple inheritance QString pyTypeBasesVariable = chopType(pyTypeName) + u"_Type_bases"_s; - const auto &baseClasses = metaClass->typeSystemBaseClasses(); - if (metaClass->baseClassNames().size() > 1) { - s << "PyObject *" << pyTypeBasesVariable - << " = PyTuple_Pack(" << baseClasses.size() << ',' << '\n' << indent; - for (qsizetype i = 0, size = baseClasses.size(); i < size; ++i) { - if (i) - s << ",\n"; - s << "reinterpret_cast(" - << cpythonTypeNameExt(baseClasses.at(i)->typeEntry()) << ')'; - } - s << ");\n\n" << outdent; + const QStringList pyBases = pyBaseTypes(metaClass); + s << "Shiboken::AutoDecRef " << pyTypeBasesVariable << "(PyTuple_Pack(" + << pyBases.size() << ",\n" << indent; + for (qsizetype i = 0, size = pyBases.size(); i < size; ++i) { + if (i) + s << ",\n"; + s << pyBases.at(i); } + s << "));\n\n" << outdent; // Create type and insert it in the module or enclosing class. const QString typePtr = u"_"_s + chopType(pyTypeName) @@ -5391,29 +5577,11 @@ void CppGenerator::writeClassRegister(TextStream &s, if (dtorClassName.isEmpty()) s << "nullptr,\n"; else - s << "&Shiboken::callCppDestructor< ::" << dtorClassName << " >,\n"; - - // 6:baseType: Find a type that is not disabled. - auto base = metaClass->isNamespace() - ? metaClass->extendedNamespace() : metaClass->baseClass(); - if (!metaClass->isNamespace()) { - for (; base != nullptr; base = base->baseClass()) { - const auto ct = base->typeEntry()->codeGeneration(); - if (ct == TypeEntry::GenerateCode || ct == TypeEntry::GenerateForSubclass) - break; - } - } - if (base) { - s << cpythonTypeNameExt(base->typeEntry()) << ",\n"; - } else { - s << "nullptr,\n"; - } + s << "&Shiboken::callCppDestructor< " << globalScopePrefix(classContext) + << dtorClassName << " >,\n"; // 7:baseTypes - if (metaClass->baseClassNames().size() > 1) - s << pyTypeBasesVariable << ',' << '\n'; - else - s << "0,\n"; + s << pyTypeBasesVariable << ".object()," << '\n'; // 8:wrapperflags QByteArrayList wrapperFlags; @@ -5421,6 +5589,8 @@ void CppGenerator::writeClassRegister(TextStream &s, wrapperFlags.append(QByteArrayLiteral("Shiboken::ObjectType::WrapperFlags::InnerClass")); if (metaClass->deleteInMainThread()) wrapperFlags.append(QByteArrayLiteral("Shiboken::ObjectType::WrapperFlags::DeleteInMainThread")); + if (classTypeEntry->isValue()) + wrapperFlags.append("Shiboken::ObjectType::WrapperFlags::Value"_ba); if (wrapperFlags.isEmpty()) s << '0'; else @@ -5433,11 +5603,7 @@ void CppGenerator::writeClassRegister(TextStream &s, if (usePySideExtensions() && !classContext.forSmartPointer()) s << "SbkObjectType_SetPropertyStrings(pyType, " << chopType(pyTypeName) << "_PropertyStrings);\n"; - - if (!classContext.forSmartPointer()) - s << cpythonTypeNameExt(classTypeEntry) << " = pyType;\n\n"; - else - s << cpythonTypeNameExt(classContext.preciseType()) << " = pyType;\n\n"; + s << globalTypeVarExpr << " = pyType;\n\n"; // Register conversions for the type. writeConverterRegister(s, metaClass, classContext); @@ -5503,22 +5669,38 @@ void CppGenerator::writeClassRegister(TextStream &s, if (usePySideExtensions() && isQObject(metaClass)) { s << "Shiboken::ObjectType::setSubTypeInitHook(pyType, &PySide::initQObjectSubType);\n" - << "PySide::initDynamicMetaObject(pyType, &::" + << "PySide::initDynamicMetaObject(pyType, &" << m_gsp << metaClass->qualifiedCppName() << "::staticMetaObject, sizeof(" << (shouldGenerateCppWrapper(metaClass) ? wrapperName(metaClass) : getFullTypeName(metaClass)) << "));\n"; } - s << outdent << "}\n"; + s << "\nreturn pyType;\n" << outdent << "}\n"; } void CppGenerator::writeStaticFieldInitialization(TextStream &s, const AbstractMetaClassCPtr &metaClass) { - s << "\nvoid " << getSimpleClassStaticFieldsInitFunctionName(metaClass) - << "()\n{\n" << indent << "Shiboken::AutoDecRef dict(PepType_GetDict(reinterpret_cast(" - << cpythonTypeName(metaClass) << ")));\n"; + // cpythonTypeName == "Sbk_QRhiShaderResourceBinding_Data_TypeF" + QString name = cpythonTypeName(metaClass); + const auto parts = QStringView{name}.split(u'_', Qt::SkipEmptyParts); + if (parts.size() < 4) { + s << "\nPyTypeObject *" << getSimpleClassStaticFieldsInitFunctionName(metaClass) + << "(PyObject *module)\n{\n" << indent + << "auto *obType = PyObject_GetAttrString(module, \"" << metaClass->name() << "\");\n" + << "auto *type = reinterpret_cast(obType);\n" + << "Shiboken::AutoDecRef dict(PepType_GetDict(type));\n"; + } else { + s << "\nPyTypeObject *" << getSimpleClassStaticFieldsInitFunctionName(metaClass) + << "(PyObject *module)\n{\n" << indent + << "auto *obContainerType = PyObject_GetAttrString(module, \"" + << parts.at(1) << "\");\n" + << "auto *obType = PyObject_GetAttrString(obContainerType, \"" + << parts.at(2) << "\");\n" + << "auto *type = reinterpret_cast(obType);\n" + << "Shiboken::AutoDecRef dict(PepType_GetDict(type));\n"; + } for (const AbstractMetaField &field : metaClass->fields()) { if (field.isStatic()) { s << "PyDict_SetItemString(dict, \"" << field.name() @@ -5527,7 +5709,7 @@ void CppGenerator::writeStaticFieldInitialization(TextStream &s, s << ");\n"; } } - s << '\n' << outdent << "}\n"; + s << "return type;\n" << outdent << "}\n"; } enum class QtRegisterMetaType @@ -5606,18 +5788,18 @@ void CppGenerator::writeInitQtMetaTypeFunctionBody(TextStream &s, const Generato case QtRegisterMetaType::None: break; case QtRegisterMetaType::Pointer: - s << "qRegisterMetaType< ::" << className << " *>();\n"; + s << "qRegisterMetaType< " << m_gsp << className << " *>();\n"; break; case QtRegisterMetaType::Value: for (const QString &name : std::as_const(nameVariants)) - s << "qRegisterMetaType< ::" << className << " >(\"" << name << "\");\n"; + s << "qRegisterMetaType< " << m_gsp << className << " >(\"" << name << "\");\n"; break; } for (const AbstractMetaEnum &metaEnum : metaClass->enums()) { if (!metaEnum.isPrivate() && !metaEnum.isAnonymous()) { for (const QString &name : std::as_const(nameVariants)) { - s << "qRegisterMetaType< ::" + s << "qRegisterMetaType< " << m_gsp << metaEnum.typeEntry()->qualifiedCppName() << " >(\"" << name << "::" << metaEnum.name() << "\");\n"; } @@ -5625,6 +5807,24 @@ void CppGenerator::writeInitQtMetaTypeFunctionBody(TextStream &s, const Generato } } +void CppGenerator::replacePolymorphicIdPlaceHolders(const AbstractMetaClassCPtr &metaClass, + QString *id) +{ + if (id->contains("%1"_L1)) { + QString replacement = " reinterpret_cast< "_L1 + m_gsp + metaClass->qualifiedCppName() + + " *>(cptr)"_L1; + id->replace("%1"_L1, replacement); + } + if (id->contains("%B"_L1)) { + auto baseClass = metaClass; + while (!baseClass->typeEntry()->isPolymorphicBase() && baseClass->baseClass()) + baseClass = baseClass->baseClass(); + QString replacement = " reinterpret_cast< "_L1 + m_gsp + baseClass->qualifiedCppName() + + " *>(cptr)"_L1; + id->replace("%B"_L1, replacement); + } +} + void CppGenerator::writeTypeDiscoveryFunction(TextStream &s, const AbstractMetaClassCPtr &metaClass) { @@ -5632,15 +5832,12 @@ void CppGenerator::writeTypeDiscoveryFunction(TextStream &s, s << "static void *" << cpythonBaseName(metaClass) << "_typeDiscovery(void *cptr, PyTypeObject *instanceType)\n{\n" << indent - << sbkUnusedVariableCast(u"cptr"_s) - << sbkUnusedVariableCast(u"instanceType"); + << sbkUnusedVariableCast("cptr") + << sbkUnusedVariableCast("instanceType"); if (!polymorphicExpr.isEmpty()) { - polymorphicExpr = polymorphicExpr.replace(u"%1"_s, - u" reinterpret_cast< ::"_s - + metaClass->qualifiedCppName() - + u" *>(cptr)"_s); - s << " if (" << polymorphicExpr << ")\n" << indent + replacePolymorphicIdPlaceHolders(metaClass, &polymorphicExpr); + s << "if (" << polymorphicExpr << ")\n" << indent << "return cptr;\n" << outdent; } else if (metaClass->isPolymorphic()) { const auto &ancestors = metaClass->allTypeSystemAncestors(); @@ -5648,7 +5845,7 @@ void CppGenerator::writeTypeDiscoveryFunction(TextStream &s, if (ancestor->baseClass()) continue; if (ancestor->isPolymorphic()) { - s << "if (instanceType == Shiboken::SbkType< ::" + s << "if (instanceType == Shiboken::SbkType< " << m_gsp << ancestor->qualifiedCppName() << " >())\n" << indent << "return dynamic_cast< " << getFullTypeName(metaClass) << " *>(reinterpret_cast< "<< getFullTypeName(ancestor) @@ -5697,7 +5894,7 @@ void CppGenerator::writeSetattroFunction(TextStream &s, AttroCheck attroCheck, if (attroCheck.testFlag(AttroCheckFlag::SetattroMethodOverride) && context.useWrapper()) { s << "if (value != nullptr && PyCallable_Check(value) != 0) {\n" << indent - << "auto plain_inst = " << cpythonWrapperCPtr(metaClass, u"self"_s) << ";\n" + << "auto plain_inst = " << cpythonWrapperCPtr(metaClass, PYTHON_SELF_VAR) << ";\n" << "auto *inst = dynamic_cast<" << context.wrapperName() << " *>(plain_inst);\n" << "if (inst != nullptr)\n" << indent << "inst->resetPyMethodCache();\n" << outdent << outdent @@ -5716,7 +5913,7 @@ void CppGenerator::writeSetattroFunction(TextStream &s, AttroCheck attroCheck, Q_ASSERT(func); s << "{\n" << indent << "auto " << CPP_SELF_VAR << " = " - << cpythonWrapperCPtr(metaClass, u"self"_s) << ";\n"; + << cpythonWrapperCPtr(metaClass, PYTHON_SELF_VAR) << ";\n"; writeClassCodeSnips(s, func->injectedCodeSnips(), TypeSystem::CodeSnipPositionAny, TypeSystem::TargetLangCode, context); s << outdent << "}\n"; @@ -5735,10 +5932,10 @@ QString CppGenerator::qObjectGetAttroFunction() const { static QString result; if (result.isEmpty()) { - auto qobjectClass = AbstractMetaClass::findClass(api().classes(), qObjectT()); + auto qobjectClass = AbstractMetaClass::findClass(api().classes(), qObjectT); Q_ASSERT(qobjectClass); result = u"PySide::getHiddenDataFromQObject("_s - + cpythonWrapperCPtr(qobjectClass, u"self"_s) + + cpythonWrapperCPtr(qobjectClass, PYTHON_SELF_VAR) + u", self, name)"_s; } return result; @@ -5770,9 +5967,11 @@ void CppGenerator::writeGetattroFunction(TextStream &s, AttroCheck attroCheck, << "Shiboken::AutoDecRef tpDict(PepType_GetDict(Py_TYPE(self)));\n" << "if (auto *meth = PyDict_GetItem(tpDict.object(), tmp)) {\n" << indent; // PYSIDE-1523: PyFunction_Check is not accepting compiled functions. - s << "if (std::strcmp(Py_TYPE(meth)->tp_name, \"compiled_function\") == 0)\n" << indent - << "return Py_TYPE(meth)->tp_descr_get(meth, self, nullptr);\n" << outdent - << "return PyFunction_Check(meth) ? PyMethod_New(meth, self)\n" + s << "if (std::strcmp(Py_TYPE(meth)->tp_name, \"compiled_function\") == 0) {\n" << indent + << "auto descrGetFunc = " + << pyTypeGetSlot("descrgetfunc", "Py_TYPE(meth)", "Py_tp_descr_get") << ";\n" + << "return descrGetFunc(meth, self, nullptr);\n" << outdent + << "}\nreturn PyFunction_Check(meth) ? PyMethod_New(meth, self)\n" << " : " << getattrFunc << ";\n" << outdent << "}\n" << outdent << "}\n"; @@ -5798,7 +5997,7 @@ void CppGenerator::writeGetattroFunction(TextStream &s, AttroCheck attroCheck, Q_ASSERT(func); s << "{\n" << indent << "auto " << CPP_SELF_VAR << " = " - << cpythonWrapperCPtr(metaClass, u"self"_s) << ";\n"; + << cpythonWrapperCPtr(metaClass, PYTHON_SELF_VAR) << ";\n"; writeClassCodeSnips(s, func->injectedCodeSnips(), TypeSystem::CodeSnipPositionAny, TypeSystem::TargetLangCode, context); s << outdent << "}\n"; @@ -5847,21 +6046,50 @@ void CppGenerator::writeNbBoolFunction(const GeneratorContext &context, // function. void CppGenerator::writeInitFunc(TextStream &declStr, TextStream &callStr, const QString &initFunctionName, - const TypeEntryCPtr &enclosingEntry) + const TypeEntryCPtr &enclosingEntry, + const QString &pythonName, bool lazy) { - const bool hasParent = - enclosingEntry && enclosingEntry->type() != TypeEntry::TypeSystemType; - declStr << "void init_" << initFunctionName << "(PyObject *" + const QString functionName = "init_"_L1 + initFunctionName; + const bool hasParent = enclosingEntry && enclosingEntry->type() != TypeEntry::TypeSystemType; + declStr << "PyTypeObject *" << functionName << "(PyObject *" << (hasParent ? "enclosingClass" : "module") << ");\n"; - callStr << "init_" << initFunctionName; - if (hasParent) { - callStr << "(reinterpret_cast(" - << cpythonTypeNameExt(enclosingEntry) << "));\n"; + + if (!lazy) { + const QString enclosing = hasParent + ? "reinterpret_cast("_L1 + cpythonTypeNameExt(enclosingEntry) + u')' + : "module"_L1; + callStr << functionName << '(' << enclosing << ");\n"; + } else if (hasParent) { + const QString &enclosingName = enclosingEntry->name(); + const auto parts = QStringView{enclosingName}.split(u"::", Qt::SkipEmptyParts); + callStr << "Shiboken::Module::AddTypeCreationFunction(" + << "module, \"" << pythonName << "\", " << functionName << ", \""; + for (qsizetype i = 0; i < parts.size(); ++i) { + if (i > 0) + callStr << "\", \""; + callStr << parts.at(i); + } + callStr << "\");\n"; } else { - callStr << "(module);\n"; + callStr << "Shiboken::Module::AddTypeCreationFunction(" + << "module, \"" << pythonName << "\", " + << "init_" << initFunctionName << ");\n"; } } +static void writeSubModuleHandling(TextStream &s, const QString &moduleName, + const QString &subModuleOf) +{ + s << "{\n" << indent + << "Shiboken::AutoDecRef parentModule(Shiboken::Module::import(\"" + << subModuleOf << "\"));\n" + << "if (parentModule.isNull())\n" << indent + << "return nullptr;\n" << outdent + << "if (PyModule_AddObject(parentModule.object(), \"" << moduleName + << "\", module) < 0)\n" + << indent << "return nullptr;\n" << outdent << outdent << "}\n"; +} + bool CppGenerator::finishGeneration() { //Generate CPython wrapper file @@ -5904,10 +6132,10 @@ bool CppGenerator::finishGeneration() } writeInitFunc(s_classInitDecl, s_classPythonDefines, getSimpleClassInitFunctionName(cls), - targetLangEnclosingEntry(te)); + targetLangEnclosingEntry(te), cls->name()); if (cls->hasStaticFields()) { - s_classInitDecl << "void " - << getSimpleClassStaticFieldsInitFunctionName(cls) << "();\n"; + s_classInitDecl << "PyTypeObject *" + << getSimpleClassStaticFieldsInitFunctionName(cls) << "(PyObject *module);\n"; classesWithStaticFields.append(cls); } if (hasConfigCondition) { @@ -5921,13 +6149,11 @@ bool CppGenerator::finishGeneration() for (const auto &smp : api().instantiatedSmartPointers()) { GeneratorContext context = contextForSmartPointer(smp.specialized, smp.type); const auto enclosingClass = context.metaClass()->enclosingClass(); - auto enclosingTypeEntry = enclosingClass - ? enclosingClass->typeEntry() - : targetLangEnclosingEntry(smp.type.typeEntry()); + auto enclosingTypeEntry = targetLangEnclosingEntry(smp.specialized->typeEntry()); writeInitFunc(s_classInitDecl, s_classPythonDefines, getInitFunctionName(context), - enclosingTypeEntry); + enclosingTypeEntry, smp.type.name()); includes.insert(smp.type.instantiations().constFirst().typeEntry()->include()); } @@ -6010,12 +6236,15 @@ bool CppGenerator::finishGeneration() s << '\n'; } + // FIXME PYSIDE-7: Remove backwards compatible structure s << "// Current module's type array.\n" - << "PyTypeObject **" << cppApiVariableName() << " = nullptr;\n" + << "Shiboken::Module::TypeInitStruct *" << cppApiVariableName() << " = nullptr;\n" + << "// Backwards compatible structure with identical indexing.\n" + << "PyTypeObject **" << cppApiVariableNameOld() << " = nullptr;\n" << "// Current module's PyObject pointer.\n" << "PyObject *" << pythonModuleObjectName() << " = nullptr;\n" << "// Current module's converter array.\n" - << "SbkConverter **" << convertersVariableName() << " = nullptr;\n"; + << "SbkConverter **" << convertersVariableName() << " = nullptr;\n\n"; const CodeSnipList snips = moduleEntry->codeSnips(); @@ -6024,14 +6253,17 @@ bool CppGenerator::finishGeneration() // cleanup staticMetaObject attribute if (usePySideExtensions()) { + QString iType = cppApiVariableName() + "[i].type"_L1; + QString iName = cppApiVariableName() + "[i].fullName"_L1; + s << "void cleanTypesAttributes() {\n" << indent << "static PyObject *attrName = Shiboken::PyName::qtStaticMetaObject();\n" - << "for (int i = 0, imax = SBK_" << moduleName() - << "_IDX_COUNT; i < imax; i++) {\n" << indent - << "PyObject *pyType = reinterpret_cast(" << cppApiVariableName() << "[i]);\n" - << "if (pyType && PyObject_HasAttr(pyType, attrName))\n" << indent + << "const int imax = SBK_" << moduleName() << "_IDX_COUNT;\n" + << "for (int i = 0; i < imax && " << iName << " != nullptr; ++i) {\n" << indent + << "auto *pyType = reinterpret_cast(" << iType << ");\n" + << "if (pyType != nullptr && PyObject_HasAttr(pyType, attrName))\n" << indent << "PyObject_SetAttr(pyType, attrName, Py_None);\n" << outdent - << outdent << "}\n" << outdent << "}\n"; + << outdent << "}\n" << outdent << "}\n\n"; } s << "// Global functions " @@ -6067,7 +6299,7 @@ bool CppGenerator::finishGeneration() if (!requiredModules.isEmpty()) s << "// Required modules' type and converter arrays.\n"; for (const QString &requiredModule : requiredModules) { - s << "PyTypeObject **" << cppApiVariableName(requiredModule) << ";\n" + s << "Shiboken::Module::TypeInitStruct *" << cppApiVariableName(requiredModule) << ";\n" << "SbkConverter **" << convertersVariableName(requiredModule) << ";\n"; } @@ -6141,6 +6373,8 @@ bool CppGenerator::finishGeneration() // PYSIDE-510: Create a signatures string for the introspection feature. writeSignatureStrings(s, signatureStream.toString(), moduleName(), "global functions"); + writeInitInheritance(s); + // Write module init function const QString globalModuleVar = pythonModuleObjectName(); s << "extern \"C\" LIBSHIBOKEN_EXPORT PyObject *PyInit_" @@ -6167,9 +6401,30 @@ bool CppGenerator::finishGeneration() int maxTypeIndex = getMaxTypeIndex() + api().instantiatedSmartPointers().size(); if (maxTypeIndex) { - s << "// Create an array of wrapper types for the current module.\n" - << "static PyTypeObject *cppApi[SBK_" << moduleName() << "_IDX_COUNT];\n" - << cppApiVariableName() << " = cppApi;\n\n"; + s << "// Create an array of wrapper types/names for the current module.\n" + << "static Shiboken::Module::TypeInitStruct cppApi[] = {\n" << indent; + + // Windows did not like an array of QString. + QStringList typeNames; + for (int idx = 0; idx < maxTypeIndex; ++idx) + typeNames.append("+++ unknown entry #"_L1 + QString::number(idx) + + " in "_L1 + moduleName()); + + collectFullTypeNamesArray(typeNames); + + for (auto typeName : typeNames) + s << "{nullptr, \"" << typeName << "\"},\n"; + + s << "{nullptr, nullptr}\n" << outdent << "};\n" + << "// The new global structure consisting of (type, name) pairs.\n" + << cppApiVariableName() << " = cppApi;\n"; + if (usePySideExtensions()) + s << "QT_WARNING_PUSH\nQT_WARNING_DISABLE_DEPRECATED\n"; + s << "// The backward compatible alias with upper case indexes.\n" + << cppApiVariableNameOld() << " = reinterpret_cast(cppApi);\n"; + if (usePySideExtensions()) + s << "QT_WARNING_POP\n"; + s << '\n'; } s << "// Create an array of primitive type converters for the current module.\n" @@ -6179,8 +6434,13 @@ bool CppGenerator::finishGeneration() << "PyObject *module = Shiboken::Module::create(\"" << moduleName() << "\", &moduledef);\n\n" << "// Make module available from global scope\n" - << globalModuleVar << " = module;\n\n" - << "// Initialize classes in the type system\n" + << globalModuleVar << " = module;\n\n"; + + const QString subModuleOf = typeDb->defaultTypeSystemType()->subModuleOf(); + if (!subModuleOf.isEmpty()) + writeSubModuleHandling(s, moduleName(), subModuleOf); + + s << "// Initialize classes in the type system\n" << s_classPythonDefines.toString(); if (!typeConversions.isEmpty()) { @@ -6194,7 +6454,7 @@ bool CppGenerator::finishGeneration() if (!containers.isEmpty()) { s << '\n'; for (const AbstractMetaType &container : containers) { - const QString converterObj = writeContainerConverterInitialization(s, container); + const QString converterObj = writeContainerConverterInitialization(s, container, api()); const auto it = opaqueContainers.constFind(container); if (it != opaqueContainers.constEnd()) { writeSetPythonToCppPointerConversion(s, converterObj, @@ -6239,14 +6499,7 @@ bool CppGenerator::finishGeneration() if (!pte->referencesType()) continue; TypeEntryCPtr referencedType = basicReferencedTypeEntry(pte); - QString converter = converterObject(referencedType); - QStringList cppSignature = pte->qualifiedCppName().split(u"::"_s, Qt::SkipEmptyParts); - while (!cppSignature.isEmpty()) { - QString signature = cppSignature.join(u"::"_s); - s << "Shiboken::Conversions::registerConverterName(" - << converter << ", \"" << signature << "\");\n"; - cppSignature.removeFirst(); - } + registerConverterInScopes(s, pte->qualifiedCppName(), converterObject(referencedType)); } s << '\n'; @@ -6260,11 +6513,12 @@ bool CppGenerator::finishGeneration() s << "\n// Static field initialization\n"; for (const auto &cls : std::as_const(classesWithStaticFields)) { ConfigurableScope configScope(s, cls->typeEntry()); - s << getSimpleClassStaticFieldsInitFunctionName(cls) << "();\n"; + s << getSimpleClassStaticFieldsInitFunctionName(cls) << "(module);\n"; } } - s << "\nif (" << shibokenErrorsOccurred << ") {\n" << indent + s << '\n' << initInheritanceFunction << "();\n" + << "\nif (" << shibokenErrorsOccurred << ") {\n" << indent << "PyErr_Print();\n" << "Py_FatalError(\"can't initialize module " << moduleName() << "\");\n" << outdent << "}\n"; @@ -6362,7 +6616,7 @@ bool CppGenerator::writeParentChildManagement(TextStream &s, const AbstractMetaF if (parentIndex == 0) { parentVariable = PYTHON_RETURN_VAR; } else if (parentIndex == -1) { - parentVariable = u"self"_s; + parentVariable = PYTHON_SELF_VAR; } else { parentVariable = usePyArgs ? pythonArgsAt(parentIndex - 1) : PYTHON_ARG; @@ -6372,7 +6626,7 @@ bool CppGenerator::writeParentChildManagement(TextStream &s, const AbstractMetaF if (childIndex == 0) { childVariable = PYTHON_RETURN_VAR; } else if (childIndex == -1) { - childVariable = u"self"_s; + childVariable = PYTHON_SELF_VAR; } else { childVariable = usePyArgs ? pythonArgsAt(childIndex - 1) : PYTHON_ARG; diff --git a/sources/shiboken6/generator/shiboken/cppgenerator.h b/sources/shiboken6/generator/shiboken/cppgenerator.h index 95ed0bf7..f369ab01 100644 --- a/sources/shiboken6/generator/shiboken/cppgenerator.h +++ b/sources/shiboken6/generator/shiboken/cppgenerator.h @@ -49,13 +49,21 @@ protected: bool finishGeneration() override; private: + struct VirtualMethodReturn + { + QString statement; + bool needsReference = false; + }; + + void generateSmartPointerClass(TextStream &s, const GeneratorContext &classContext); void generateIncludes(TextStream &s, const GeneratorContext &classContext, const IncludeGroupList &includes = {}, const AbstractMetaClassCList &innerClasses = {}) const; static void writeInitFunc(TextStream &declStr, TextStream &callStr, const QString &initFunctionName, - const TypeEntryCPtr &enclosingEntry = {}); + const TypeEntryCPtr &enclosingEntry, + const QString &pythonName, bool lazy = true); static void writeCacheResetNative(TextStream &s, const GeneratorContext &classContext); void writeConstructorNative(TextStream &s, const GeneratorContext &classContext, const AbstractMetaFunctionCPtr &func) const; @@ -75,13 +83,20 @@ private: void writeVirtualMethodNative(TextStream &s, const AbstractMetaFunctionCPtr &func, int cacheIndex) const; + void writeVirtualMethodPythonOverride(TextStream &s, + const AbstractMetaFunctionCPtr &func, + const CodeSnipList &snips, + const VirtualMethodReturn &returnStatement) const; + void writeUserAddedPythonOverride(TextStream &s, + const AbstractMetaFunctionCPtr &func) const; void writeVirtualMethodCppCall(TextStream &s, const AbstractMetaFunctionCPtr &func, const QString &funcName, const QList &snips, const AbstractMetaArgument *lastArg, const TypeEntryCPtr &retType, const QString &returnStatement, bool hasGil) const; - static QString virtualMethodReturn(TextStream &s, const ApiExtractorResult &api, - const AbstractMetaFunctionCPtr &func, - const FunctionModificationList &functionModifications); + + static VirtualMethodReturn virtualMethodReturn(const ApiExtractorResult &api, + const AbstractMetaFunctionCPtr &func, + const FunctionModificationList &functionModifications); void writeMetaObjectMethod(TextStream &s, const GeneratorContext &classContext) const; static void writeMetaCast(TextStream &s, const GeneratorContext &classContext); @@ -179,6 +194,8 @@ private: static void writeTypeCheck(TextStream& s, const std::shared_ptr &overloadData, const QString &argumentName); + static void replacePolymorphicIdPlaceHolders(const AbstractMetaClassCPtr &metaClass, + QString *id); static void writeTypeDiscoveryFunction(TextStream &s, const AbstractMetaClassCPtr &metaClass); @@ -378,10 +395,12 @@ private: static void writeSignatureStrings(TextStream &s, const QString &signatures, const QString &arrayName, const char *comment); + void writeInitInheritance(TextStream &s) const; void writeClassRegister(TextStream &s, const AbstractMetaClassCPtr &metaClass, const GeneratorContext &classContext, const QString &signatures) const; + static QStringList pyBaseTypes(const AbstractMetaClassCPtr &metaClass); static QString destructorClassName(const AbstractMetaClassCPtr &metaClass, const GeneratorContext &classContext); static void writeStaticFieldInitialization(TextStream &s, @@ -394,6 +413,7 @@ private: void writeSignatureInfo(TextStream &s, const OverloadData &overloads) const; QString signatureParameter(const AbstractMetaArgument &arg) const; + QString pythonSignature(const AbstractMetaType &type) const; /// Writes the implementation of all methods part of python sequence protocol void writeSequenceMethods(TextStream &s, const AbstractMetaClassCPtr &metaClass, @@ -459,8 +479,11 @@ private: const CustomConversionPtr &customConversion); static void writeEnumConverterInitialization(TextStream &s, const AbstractMetaEnum &metaEnum); static QString writeContainerConverterInitialization(TextStream &s, - const AbstractMetaType &type); + const AbstractMetaType &type, + const ApiExtractorResult &api); void writeSmartPointerConverterInitialization(TextStream &s, const AbstractMetaType &ype) const; + + static QString typeInitStruct(const TypeEntryCPtr &te); static void writeExtendedConverterInitialization(TextStream &s, const TypeEntryCPtr &externalType, const AbstractMetaClassCList &conversions); @@ -528,9 +551,6 @@ private: static bool hasBoolCast(const AbstractMetaClassCPtr &metaClass) { return boolCast(metaClass).has_value(); } - std::optional - findSmartPointerInstantiation(const SmartPointerTypeEntryCPtr &pointer, - const TypeEntryCPtr &pointee) const; void clearTpFuncs(); static QString chopType(QString s); diff --git a/sources/shiboken6/generator/shiboken/cppgenerator_container.cpp b/sources/shiboken6/generator/shiboken/cppgenerator_container.cpp index f746ea71..00e0cabe 100644 --- a/sources/shiboken6/generator/shiboken/cppgenerator_container.cpp +++ b/sources/shiboken6/generator/shiboken/cppgenerator_container.cpp @@ -101,20 +101,17 @@ void CppGenerator::writeOpaqueContainerValueConverter(TextStream &s, const AbstractMetaType &valueType) const { // Generate template specialization of value converter helper unless it is already there - const QString pyArg = u"pyArg"_s; - const QString cppArg = u"cppArg"_s; - const QString valueTypeName = valueType.cppSignature(); const QString checkFunction = cpythonCheckFunction(valueType); s << "template <>\nstruct ShibokenContainerValueConverter<" << valueTypeName << ">\n{\n"; // Type check - s << indent << "static bool checkValue(PyObject *" << pyArg << ")\n{\n" + s << indent << "static bool checkValue(PyObject *" << PYTHON_ARG << ")\n{\n" << indent << "return " << checkFunction; if (!checkFunction.contains(u'(')) s << '('; - s << pyArg << ");\n" + s << PYTHON_ARG << ");\n" << outdent << "}\n\n"; // C++ to Python @@ -126,21 +123,21 @@ void CppGenerator::writeOpaqueContainerValueConverter(TextStream &s, s << valueTypeName << ' '; if (passByConstRef) s << '&'; - s << cppArg << ")\n{\n" << indent << "return "; - writeToPythonConversion(s, valueType, nullptr, cppArg); + s << CPP_ARG << ")\n{\n" << indent << "return "; + writeToPythonConversion(s, valueType, nullptr, CPP_ARG); s << ";\n" << outdent << "}\n\n"; // Python to C++ s << "static std::optional<" << valueTypeName << "> convertValueToCpp(PyObject *" - << pyArg << ")\n{\n" << indent; + << PYTHON_ARG << ")\n{\n" << indent; s << PYTHON_TO_CPPCONVERSION_STRUCT << ' ' << PYTHON_TO_CPP_VAR << ";\n" << "if (!("; - writeTypeCheck(s, valueType, pyArg), isNumber(valueType.typeEntry()); + writeTypeCheck(s, valueType, PYTHON_ARG), isNumber(valueType.typeEntry()); s << ")) {\n" << indent << "Shiboken::Errors::setWrongContainerType();\n" << "return {};\n" << outdent << "}\n"; - writePythonToCppTypeConversion(s, valueType, pyArg, cppArg, nullptr, {}); - s << "return " << cppArg << ";\n" << outdent << "}\n" << outdent << "};\n\n"; + writePythonToCppTypeConversion(s, valueType, PYTHON_ARG, CPP_ARG, nullptr, {}); + s << "return " << CPP_ARG << ";\n" << outdent << "}\n" << outdent << "};\n\n"; } // Generate code for a type wrapping a C++ container instantiation @@ -245,26 +242,25 @@ CppGenerator::OpaqueContainerData // Check function result.checkFunctionName = result.name + u"_Check"_s; - const QString pyArg = u"pyArg"_s; - s << "extern \"C\" int " << result.checkFunctionName << "(PyObject *" << pyArg - << ")\n{\n" << indent << "return " << pyArg << " != nullptr && " - << pyArg << " != Py_None && " << pyArg << "->ob_type == " + s << "extern \"C\" int " << result.checkFunctionName << "(PyObject *" << PYTHON_ARG + << ")\n{\n" << indent << "return " << PYTHON_ARG << " != nullptr && " + << PYTHON_ARG << " != Py_None && " << PYTHON_ARG << "->ob_type == " << typeFName << "();\n" << outdent << "}\n\n"; // SBK converter Python to C++ result.pythonToConverterFunctionName = u"PythonToCpp"_s + result.name; s << "extern \"C\" void " << result.pythonToConverterFunctionName - << "(PyObject *" << pyArg << ", void *cppOut)\n{\n" << indent + << "(PyObject *" << PYTHON_ARG << ", void *cppOut)\n{\n" << indent << "auto *d = ShibokenSequenceContainerPrivate<" << cppSignature - << ">::get(" << pyArg << ");\n" + << ">::get(" << PYTHON_ARG << ");\n" << "*reinterpret_cast<" << cppSignature << "**>(cppOut) = d->m_list;\n" << outdent << "}\n\n"; // SBK check function for converting Python to C++ that returns the converter result.converterCheckFunctionName = u"is"_s + result.name + u"PythonToCppConvertible"_s; s << "extern \"C\" PythonToCppFunc " << result.converterCheckFunctionName - << "(PyObject *" << pyArg << ")\n{\n" << indent << "if (" - << result.checkFunctionName << '(' << pyArg << "))\n" << indent + << "(PyObject *" << PYTHON_ARG << ")\n{\n" << indent << "if (" + << result.checkFunctionName << '(' << PYTHON_ARG << "))\n" << indent << "return " << result.pythonToConverterFunctionName << ";\n" << outdent << "return {};\n" << outdent << "}\n\n"; diff --git a/sources/shiboken6/generator/shiboken/cppgenerator_smartpointer.cpp b/sources/shiboken6/generator/shiboken/cppgenerator_smartpointer.cpp index 1b893640..a00c2ba5 100644 --- a/sources/shiboken6/generator/shiboken/cppgenerator_smartpointer.cpp +++ b/sources/shiboken6/generator/shiboken/cppgenerator_smartpointer.cpp @@ -86,18 +86,6 @@ static ComparisonOperatorList smartPointeeComparisons(const GeneratorContext &co return result; } -std::optional - CppGenerator::findSmartPointerInstantiation(const SmartPointerTypeEntryCPtr &pointer, - const TypeEntryCPtr &pointee) const -{ - for (const auto &smp : api().instantiatedSmartPointers()) { - const auto &i = smp.type; - if (i.typeEntry() == pointer && i.instantiations().at(0).typeEntry() == pointee) - return i; - } - return {}; -} - static bool hasParameterPredicate(const AbstractMetaFunctionCPtr &f) { return !f->arguments().isEmpty(); @@ -252,8 +240,8 @@ void CppGenerator::writeSmartPointerConverterFunctions(TextStream &s, for (const auto &base : baseClasses) { auto baseTe = base->typeEntry(); if (smartPointerTypeEntry->matchesInstantiation(baseTe)) { - if (auto opt = findSmartPointerInstantiation(smartPointerTypeEntry, baseTe)) { - const auto smartTargetType = opt.value(); + if (auto opt = api().findSmartPointerInstantiation(smartPointerTypeEntry, baseTe)) { + const auto &smartTargetType = opt.value().type; s << "// SmartPointer derived class: " << smartTargetType.cppSignature() << "\n"; writePythonToCppConversionFunctions(s, smartPointerType, @@ -308,8 +296,8 @@ void CppGenerator::writeSmartPointerConverterInitialization(TextStream &s, for (const auto &base : classes) { auto baseTe = base->typeEntry(); - if (auto opt = findSmartPointerInstantiation(smartPointerTypeEntry, baseTe)) { - const auto smartTargetType = opt.value(); + if (auto opt = api().findSmartPointerInstantiation(smartPointerTypeEntry, baseTe)) { + const auto &smartTargetType = opt.value().type; s << "// Convert to SmartPointer derived class: [" << smartTargetType.cppSignature() << "]\n"; const QString converter = u"Shiboken::Conversions::getConverter(\""_s diff --git a/sources/shiboken6/generator/shiboken/ctypenames.h b/sources/shiboken6/generator/shiboken/ctypenames.h index 0444c99f..f665b30f 100644 --- a/sources/shiboken6/generator/shiboken/ctypenames.h +++ b/sources/shiboken6/generator/shiboken/ctypenames.h @@ -6,26 +6,26 @@ #include -static inline QString boolT() { return QStringLiteral("bool"); } -static inline QString intT() { return QStringLiteral("int"); } -static inline QString unsignedT() { return QStringLiteral("unsigned"); } -static inline QString unsignedIntT() { return QStringLiteral("unsigned int"); } -static inline QString longT() { return QStringLiteral("long"); } -static inline QString unsignedLongT() { return QStringLiteral("unsigned long"); } -static inline QString shortT() { return QStringLiteral("short"); } -static inline QString unsignedShortT() { return QStringLiteral("unsigned short"); } -static inline QString unsignedCharT() { return QStringLiteral("unsigned char"); } -static inline QString longLongT() { return QStringLiteral("long long"); } -static inline QString unsignedLongLongT() { return QStringLiteral("unsigned long long"); } -static inline QString charT() { return QStringLiteral("char"); } -static inline QString floatT() { return QStringLiteral("float"); } -static inline QString doubleT() { return QStringLiteral("double"); } -static inline QString constCharPtrT() { return QStringLiteral("const char*"); } +constexpr auto boolT = QLatin1StringView("bool"); +constexpr auto intT = QLatin1StringView("int"); +constexpr auto unsignedT = QLatin1StringView("unsigned"); +constexpr auto unsignedIntT = QLatin1StringView("unsigned int"); +constexpr auto longT = QLatin1StringView("long"); +constexpr auto unsignedLongT = QLatin1StringView("unsigned long"); +constexpr auto shortT = QLatin1StringView("short"); +constexpr auto unsignedShortT = QLatin1StringView("unsigned short"); +constexpr auto unsignedCharT = QLatin1StringView("unsigned char"); +constexpr auto longLongT = QLatin1StringView("long long"); +constexpr auto unsignedLongLongT = QLatin1StringView("unsigned long long"); +constexpr auto charT = QLatin1StringView("char"); +constexpr auto floatT = QLatin1StringView("float"); +constexpr auto doubleT = QLatin1StringView("double"); +constexpr auto constCharPtrT = QLatin1StringView("const char*"); -static inline QString qByteArrayT() { return QStringLiteral("QByteArray"); } -static inline QString qMetaObjectT() { return QStringLiteral("QMetaObject"); } -static inline QString qObjectT() { return QStringLiteral("QObject"); } -static inline QString qStringT() { return QStringLiteral("QString"); } -static inline QString qVariantT() { return QStringLiteral("QVariant"); } +constexpr auto qByteArrayT = QLatin1StringView("QByteArray"); +constexpr auto qMetaObjectT = QLatin1StringView("QMetaObject"); +constexpr auto qObjectT = QLatin1StringView("QObject"); +constexpr auto qStringT = QLatin1StringView("QString"); +constexpr auto qVariantT = QLatin1StringView("QVariant"); #endif // CTYPENAMES_H diff --git a/sources/shiboken6/generator/shiboken/generatorstrings.h b/sources/shiboken6/generator/shiboken/generatorstrings.h index 6751d521..9ce91e59 100644 --- a/sources/shiboken6/generator/shiboken/generatorstrings.h +++ b/sources/shiboken6/generator/shiboken/generatorstrings.h @@ -6,25 +6,30 @@ #include -QString CPP_ARG(int i); +QString CPP_ARG_N(int i); QString CPP_ARG_REMOVED(int i); -extern const QString CPP_RETURN_VAR; -extern const QString CPP_SELF_VAR; -extern const QString NULL_PTR; -extern const QString PYTHON_ARG; -extern const QString PYTHON_ARGS; -extern const QString PYTHON_OVERRIDE_VAR; -extern const QString PYTHON_RETURN_VAR; -extern const QString PYTHON_TO_CPP_VAR; +constexpr auto CPP_RETURN_VAR = QLatin1StringView("cppResult"); +constexpr auto CPP_SELF_VAR = QLatin1StringView("cppSelf"); +constexpr auto CPP_ARG = QLatin1StringView("cppArg"); +constexpr auto NULL_PTR = QLatin1StringView("nullptr"); +constexpr auto PYTHON_ARG = QLatin1StringView("pyArg"); +constexpr auto PYTHON_ARGS = QLatin1StringView("pyArgs"); +constexpr auto PYTHON_OVERRIDE_VAR = QLatin1StringView("pyOverride"); +constexpr auto PYTHON_RETURN_VAR = QLatin1StringView("pyResult"); +constexpr auto PYTHON_SELF_VAR = QLatin1StringView("self"); +constexpr auto PYTHON_TO_CPP_VAR = QLatin1StringView("pythonToCpp"); -extern const QString CONV_RULE_OUT_VAR_SUFFIX; -extern const QString BEGIN_ALLOW_THREADS; -extern const QString END_ALLOW_THREADS; +constexpr auto CONV_RULE_OUT_VAR_SUFFIX = QLatin1StringView("_out"); +constexpr auto BEGIN_ALLOW_THREADS + = QLatin1StringView("PyThreadState *_save = PyEval_SaveThread(); // Py_BEGIN_ALLOW_THREADS"); +constexpr auto END_ALLOW_THREADS + = QLatin1StringView("PyEval_RestoreThread(_save); // Py_END_ALLOW_THREADS"); -extern const QString REPR_FUNCTION; +constexpr auto REPR_FUNCTION = QLatin1StringView("__repr__"); + +constexpr auto CPP_ARG0 = QLatin1StringView("cppArg0"); -extern const QString CPP_ARG0; extern const char *const METHOD_DEF_SENTINEL; extern const char *const PYTHON_TO_CPPCONVERSION_STRUCT; extern const char *const openTargetExternC; diff --git a/sources/shiboken6/generator/shiboken/headergenerator.cpp b/sources/shiboken6/generator/shiboken/headergenerator.cpp index 9f6de356..35d0d114 100644 --- a/sources/shiboken6/generator/shiboken/headergenerator.cpp +++ b/sources/shiboken6/generator/shiboken/headergenerator.cpp @@ -39,6 +39,22 @@ using namespace Qt::StringLiterals; +struct IndexValue +{ + QString name; // "SBK_..." + int value; + QString comment; +}; + +TextStream &operator<<(TextStream &s, const IndexValue &iv) +{ + s << " " << AlignedField(iv.name, 56) << " = " << iv.value << ','; + if (!iv.comment.isEmpty()) + s << " // " << iv.comment; + s << '\n'; + return s; +} + // PYSIDE-504: Handling the "protected hack" // The problem: Creating wrappers when the class has private destructors. // You can see an example on Windows in qclipboard_wrapper.h and others. @@ -101,7 +117,7 @@ void HeaderGenerator::generateClass(TextStream &s, const GeneratorContext &class s << licenseComment(); QString wrapperName = classContext.effectiveClassName(); - QString outerHeaderGuard = getFilteredCppSignatureString(wrapperName).toUpper(); + QString outerHeaderGuard = getFilteredCppSignatureString(wrapperName); // Header s << "#ifndef SBK_" << outerHeaderGuard << "_H\n"; @@ -110,7 +126,7 @@ void HeaderGenerator::generateClass(TextStream &s, const GeneratorContext &class if (!avoidProtectedHack()) s << protectedHackDefine; - //Includes + // Includes s << metaClass->typeEntry()->include() << '\n'; for (auto &inst : metaClass->templateBaseClassInstantiations()) s << inst.typeEntry()->include(); @@ -240,10 +256,10 @@ void HeaderGenerator::writeWrapperClassDeclaration(TextStream &s, TypeSystem::CodeSnipPositionDeclaration, TypeSystem::NativeCode, classContext); - if ((!avoidProtectedHack() || !metaClass->hasPrivateDestructor()) - && usePySideExtensions() && isQObject(metaClass)) { - s << outdent << "public:\n" << indent << - R"(int qt_metacall(QMetaObject::Call call, int id, void **args) override; + if (shouldGenerateMetaObjectFunctions(metaClass)) { + s << R"( +const ::QMetaObject * metaObject() const override; +int qt_metacall(QMetaObject::Call call, int id, void **args) override; void *qt_metacast(const char *_clname) override; )"; } @@ -258,8 +274,15 @@ void *qt_metacast(const char *_clname) override; s << "static void pysideInitQtMetaTypes();\n"; s << "void resetPyMethodCache();\n" - << outdent << "private:\n" << indent - << "mutable bool m_PyMethodCache[" << maxOverrides << "];\n" + << outdent << "private:\n" << indent; + + if (!metaClass->userAddedPythonOverrides().isEmpty()) { + for (const auto &f : metaClass->userAddedPythonOverrides()) + s << functionSignature(f, {}, {}, Generator::OriginalTypeDescription) << ";\n"; + s << '\n'; + } + + s << "mutable bool m_PyMethodCache[" << maxOverrides << "];\n" << outdent << "};\n\n"; } @@ -270,8 +293,6 @@ void HeaderGenerator::writeMemberFunctionWrapper(TextStream &s, { Q_ASSERT(!func->isConstructor() && !func->isOperatorOverload()); s << "inline "; - if (func->isStatic()) - s << "static "; s << functionSignature(func, {}, postfix, Generator::OriginalTypeDescription) << " { "; if (!func->isVoid()) @@ -330,7 +351,7 @@ void HeaderGenerator::writeFunction(TextStream &s, const AbstractMetaFunctionCPt } const bool isVirtual = generation.testFlag(FunctionGenerationFlag::VirtualMethod); - if (isVirtual || generation.testFlag(FunctionGenerationFlag::QMetaObjectMethod)) { + if (isVirtual) { s << functionSignature(func, {}, {}, Generator::OriginalTypeDescription) << " override;\n"; } @@ -355,20 +376,6 @@ void HeaderGenerator::writeFunction(TextStream &s, const AbstractMetaFunctionCPt } } -static void _writeTypeIndexValue(TextStream &s, const QString &variableName, - int typeIndex) -{ - s << " " << AlignedField(variableName, 56) << " = " << typeIndex; -} - -static inline void _writeTypeIndexValueLine(TextStream &s, - const QString &variableName, - int typeIndex) -{ - _writeTypeIndexValue(s, variableName, typeIndex); - s << ",\n"; -} - // Find equivalent typedefs "using Foo=QList", "using Bar=QList" static AbstractMetaClassCPtr findEquivalentTemplateTypedef(const AbstractMetaClassCList &haystack, @@ -386,14 +393,15 @@ static AbstractMetaClassCPtr return nullptr; } -void HeaderGenerator::writeTypeIndexValueLine(TextStream &s, const ApiExtractorResult &api, - const TypeEntryCPtr &typeEntry) +void HeaderGenerator::collectTypeEntryTypeIndexes(const ApiExtractorResult &api, + const TypeEntryCPtr &typeEntry, + IndexValues *indexValues) { if (!typeEntry || !typeEntry->generateCode()) return; - s.setFieldAlignment(QTextStream::AlignLeft); const int typeIndex = typeEntry->sbkIndex(); - _writeTypeIndexValueLine(s, getTypeIndexVariableName(typeEntry), typeIndex); + indexValues->append({getTypeIndexVariableName(typeEntry), typeIndex, {}}); + if (typeEntry->isComplex()) { // For a typedef "using Foo=QList", write a type index // SBK_QLIST_INT besides SBK_FOO which is then matched by function @@ -408,7 +416,7 @@ void HeaderGenerator::writeTypeIndexValueLine(TextStream &s, const ApiExtractorR metaClass) == nullptr) { const QString indexVariable = getTypeAlternateTemplateIndexVariableName(metaClass); - _writeTypeIndexValueLine(s, indexVariable, typeIndex); + indexValues->append({indexVariable, typeIndex, {}}); m_alternateTemplateIndexes.append(m_alternateTemplateIndexes); } } @@ -416,12 +424,13 @@ void HeaderGenerator::writeTypeIndexValueLine(TextStream &s, const ApiExtractorR if (typeEntry->isEnum()) { auto ete = std::static_pointer_cast(typeEntry); if (ete->flags()) - writeTypeIndexValueLine(s, api, ete->flags()); + collectTypeEntryTypeIndexes(api, ete->flags(), indexValues); } } -void HeaderGenerator::writeTypeIndexValueLines(TextStream &s, const ApiExtractorResult &api, - const AbstractMetaClassCPtr &metaClass) +void HeaderGenerator::collectClassTypeIndexes(const ApiExtractorResult &api, + const AbstractMetaClassCPtr &metaClass, + IndexValues *indexValues) { auto typeEntry = metaClass->typeEntry(); if (!typeEntry->generateCode()) @@ -429,10 +438,10 @@ void HeaderGenerator::writeTypeIndexValueLines(TextStream &s, const ApiExtractor // enum indices are required for invisible namespaces as well. for (const AbstractMetaEnum &metaEnum : metaClass->enums()) { if (!metaEnum.isPrivate()) - writeTypeIndexValueLine(s, api, metaEnum.typeEntry()); + collectTypeEntryTypeIndexes(api, metaEnum.typeEntry(), indexValues); } if (NamespaceTypeEntry::isVisibleScope(typeEntry)) - writeTypeIndexValueLine(s, api, typeEntry); + collectTypeEntryTypeIndexes(api, typeEntry, indexValues); } // Format the typedefs for the typedef entries to be generated @@ -537,6 +546,11 @@ static void writeForwardDeclarations(TextStream &s, { NameSpaces nameSpaces; + s << '\n'; + auto typeSystemEntry = TypeDatabase::instance()->defaultTypeSystemType(); + if (!typeSystemEntry->namespaceBegin().isEmpty()) + s << typeSystemEntry->namespaceBegin() << '\n'; + for (const auto &c : classList) { if (auto encl = c->enclosingClass()) { Q_ASSERT(encl->isNamespace()); @@ -565,6 +579,9 @@ static void writeForwardDeclarations(TextStream &s, if (nsp.nameSpace->enclosingClass() == nullptr) writeNamespaceForwardDeclarationRecursion(s, i, nameSpaces); } + + if (!typeSystemEntry->namespaceEnd().isEmpty()) + s << typeSystemEntry->namespaceEnd() << '\n'; } // Include parameters required for the module/private module header @@ -586,91 +603,132 @@ struct ModuleHeaderParameters QString typeFunctions; }; -bool HeaderGenerator::finishGeneration() +HeaderGenerator::IndexValues + HeaderGenerator::collectTypeIndexes(const AbstractMetaClassCList &classList) { - // Generate the main header for this module. This header should be included - // by binding modules extending on top of this one. - ModuleHeaderParameters parameters; - ModuleHeaderParameters privateParameters; - StringStream macrosStream(TextStream::Language::Cpp); - - const auto snips = TypeDatabase::instance()->defaultTypeSystemType()->codeSnips(); - writeModuleCodeSnips(macrosStream, snips, TypeSystem::CodeSnipPositionDeclaration, - TypeSystem::TargetLangCode); - - macrosStream << "// Type indices\nenum : int {\n"; - auto classList = api().classes(); - - std::sort(classList.begin(), classList.end(), - [](const AbstractMetaClassCPtr &a, const AbstractMetaClassCPtr &b) { - return a->typeEntry()->sbkIndex() < b->typeEntry()->sbkIndex(); - }); + IndexValues result; for (const auto &metaClass : classList) - writeTypeIndexValueLines(macrosStream, api(), metaClass); + collectClassTypeIndexes(api(), metaClass, &result); for (const AbstractMetaEnum &metaEnum : api().globalEnums()) - writeTypeIndexValueLine(macrosStream, api(), metaEnum.typeEntry()); + collectTypeEntryTypeIndexes(api(), metaEnum.typeEntry(), &result); // Write the smart pointer define indexes. int smartPointerCountIndex = getMaxTypeIndex(); int smartPointerCount = 0; for (const auto &smp : api().instantiatedSmartPointers()) { QString indexName = getTypeIndexVariableName(smp.type); - _writeTypeIndexValue(macrosStream, indexName, smartPointerCountIndex); - macrosStream << ", // " << smp.type.cppSignature() << '\n'; + result.append({indexName, smartPointerCountIndex, smp.type.cppSignature()}); // Add a the same value for const pointees (shared_ptr). const auto ptrName = smp.type.typeEntry()->entryName(); const auto pos = indexName.indexOf(ptrName, 0, Qt::CaseInsensitive); if (pos >= 0) { - indexName.insert(pos + ptrName.size() + 1, u"CONST"_s); - _writeTypeIndexValue(macrosStream, indexName, smartPointerCountIndex); - macrosStream << ", // (const)\n"; + indexName.insert(pos + ptrName.size() + 1, u"const"_s); + result.append({indexName, smartPointerCountIndex, "(const)"_L1}); } ++smartPointerCountIndex; ++smartPointerCount; } + result.append({"SBK_"_L1 + moduleName() + "_IDX_COUNT"_L1, + getMaxTypeIndex() + smartPointerCount, {}}); + return result; +} - _writeTypeIndexValue(macrosStream, - u"SBK_"_s + moduleName() + u"_IDX_COUNT"_s, - getMaxTypeIndex() + smartPointerCount); - macrosStream << "\n};\n"; - - macrosStream << "// This variable stores all Python types exported by this module.\n"; - macrosStream << "extern PyTypeObject **" << cppApiVariableName() << ";\n\n"; - macrosStream << "// This variable stores the Python module object exported by this module.\n"; - macrosStream << "extern PyObject *" << pythonModuleObjectName() << ";\n\n"; - macrosStream << "// This variable stores all type converters exported by this module.\n"; - macrosStream << "extern SbkConverter **" << convertersVariableName() << ";\n\n"; - - // TODO-CONVERTER ------------------------------------------------------------------------------ - // Using a counter would not do, a fix must be made to APIExtractor's getTypeIndex(). - macrosStream << "// Converter indices\nenum : int {\n"; +HeaderGenerator::IndexValues HeaderGenerator::collectConverterIndexes() const +{ + IndexValues result; const auto &primitives = primitiveTypes(); int pCount = 0; for (const auto &ptype : primitives) { - /* Note: do not generate indices for typedef'd primitive types - * as they'll use the primitive type converters instead, so we - * don't need to create any other. - */ - if (!ptype->generateCode() || !ptype->customConversion()) - continue; - - _writeTypeIndexValueLine(macrosStream, getTypeIndexVariableName(ptype), pCount++); + // Note: do not generate indices for typedef'd primitive types as + // they'll use the primitive type converters instead, so we + // don't need to create any other. + if (ptype->generateCode() && ptype->customConversion() != nullptr) + result.append({getTypeIndexVariableName(ptype), pCount++, {}}); } for (const AbstractMetaType &container : api().instantiatedContainers()) { - _writeTypeIndexValue(macrosStream, getTypeIndexVariableName(container), pCount); - macrosStream << ", // " << container.cppSignature() << '\n'; - pCount++; + result.append({getTypeIndexVariableName(container), + pCount++, container.cppSignature()}); } // Because on win32 the compiler will not accept a zero length array. if (pCount == 0) pCount++; - _writeTypeIndexValue(macrosStream, QStringLiteral("SBK_%1_CONVERTERS_IDX_COUNT") - .arg(moduleName()), pCount); - macrosStream << "\n};\n"; + result.append({"SBK_"_L1 + moduleName() + "_CONVERTERS_IDX_COUNT"_L1, + pCount, {}}); + return result; +} + +// PYSIDE-2404: Write the enums in unchanged case for reuse in type imports. +// For conpatibility, we create them in uppercase, too and with +// doubled index for emulating the former type-only case. +// +// FIXME: Remove in PySide 7. (See the note in `parser.py`) +// +static IndexValue typeIndexUpper(struct IndexValue const &ti) +{ + QString modi = ti.name.toUpper(); + if (modi == ti.name) + modi = u"// "_s + modi; + return {modi, ti.value * 2, ti.comment}; +} + +bool HeaderGenerator::finishGeneration() +{ + // Generate the main header for this module. This header should be included + // by binding modules extending on top of this one. + ModuleHeaderParameters parameters; + ModuleHeaderParameters privateParameters; + StringStream macrosStream(TextStream::Language::Cpp); + + const auto snips = TypeDatabase::instance()->defaultTypeSystemType()->codeSnips(); + writeModuleCodeSnips(macrosStream, snips, TypeSystem::CodeSnipPositionDeclaration, + TypeSystem::TargetLangCode); + + auto classList = api().classes(); + + std::sort(classList.begin(), classList.end(), + [](const AbstractMetaClassCPtr &a, const AbstractMetaClassCPtr &b) { + return a->typeEntry()->sbkIndex() < b->typeEntry()->sbkIndex(); + }); + + const auto typeIndexes = collectTypeIndexes(classList); + + macrosStream << "\n// Type indices\nenum [[deprecated]] : int {\n"; + for (const auto &ti : typeIndexes) + macrosStream << typeIndexUpper(ti); + macrosStream << "};\n"; + + macrosStream << "\n// Type indices\nenum : int {\n"; + for (const auto &ti : typeIndexes) + macrosStream << ti; + macrosStream << "};\n\n"; + + // FIXME: Remove backwards compatible variable in PySide 7. + macrosStream << "// This variable stores all Python types exported by this module.\n"; + macrosStream << "extern Shiboken::Module::TypeInitStruct *" << cppApiVariableName() << ";\n\n"; + macrosStream << "// This variable stores all Python types exported by this module "; + macrosStream << "in a backwards compatible way with identical indexing.\n"; + macrosStream << "[[deprecated]] extern PyTypeObject **" << cppApiVariableNameOld() << ";\n\n"; + macrosStream << "// This variable stores the Python module object exported by this module.\n"; + macrosStream << "extern PyObject *" << pythonModuleObjectName() << ";\n\n"; + macrosStream << "// This variable stores all type converters exported by this module.\n"; + macrosStream << "extern SbkConverter **" << convertersVariableName() << ";\n\n"; + + // TODO-CONVERTER ------------------------------------------------------------------------------ + // Using a counter would not do, a fix must be made to APIExtractor's getTypeIndex(). + const auto converterIndexes = collectConverterIndexes(); + macrosStream << "// Converter indices\nenum [[deprecated]] : int {\n"; + for (const auto &ci : converterIndexes) + macrosStream << typeIndexUpper(ci); + macrosStream << "};\n\n"; + + macrosStream << "// Converter indices\nenum : int {\n"; + for (const auto &ci : converterIndexes) + macrosStream << ci; + macrosStream << "};\n"; formatTypeDefEntries(macrosStream); @@ -698,7 +756,7 @@ bool HeaderGenerator::finishGeneration() if (!shouldGenerate(classType)) continue; - //Includes + // Includes const bool isPrivate = classType->isPrivate(); auto &par = isPrivate ? privateParameters : parameters; const auto classInclude = classType->include(); @@ -752,6 +810,7 @@ bool HeaderGenerator::finishGeneration() } s << "#include \n"; + s << "#include \n"; s << "#include \n"; QStringList requiredTargetImports = TypeDatabase::instance()->requiredTargetImports(); @@ -816,8 +875,7 @@ void HeaderGenerator::writePrivateHeader(const QString &moduleHeaderDir, TextStream &ps = privateFile.stream; ps.setLanguage(TextStream::Language::Cpp); QString privateIncludeShield = - publicIncludeShield.left(publicIncludeShield.size() - 2) - + QStringLiteral("_P_H"); + publicIncludeShield.left(publicIncludeShield.size() - 2) + "_P_H"_L1; ps << licenseComment()<< "\n\n"; @@ -868,12 +926,12 @@ void HeaderGenerator::writeSbkTypeFunction(TextStream &s, const AbstractMetaEnum : cppEnum.qualifiedCppName(); const auto te = cppEnum.typeEntry(); ConfigurableScope configScope(s, te); - s << "template<> inline PyTypeObject *SbkType< ::" << enumName << " >() "; + s << "template<> inline PyTypeObject *SbkType< " << m_gsp << enumName << " >() "; s << "{ return " << cpythonTypeNameExt(te) << "; }\n"; const auto flag = cppEnum.typeEntry()->flags(); if (flag) { - s << "template<> inline PyTypeObject *SbkType< ::" << flag->name() << " >() " + s << "template<> inline PyTypeObject *SbkType< " << m_gsp << flag->name() << " >() " << "{ return " << cpythonTypeNameExt(flag) << "; }\n"; } } @@ -882,13 +940,13 @@ void HeaderGenerator::writeSbkTypeFunction(TextStream &s, const AbstractMetaClas { s << "template<> inline PyTypeObject *SbkType< " << getFullTypeName(cppClass) << " >() " - << "{ return reinterpret_cast(" - << cpythonTypeNameExt(cppClass->typeEntry()) << "); }\n"; + << "{ return " << cpythonTypeNameExt(cppClass->typeEntry()) << "; }\n"; } void HeaderGenerator::writeSbkTypeFunction(TextStream &s, const AbstractMetaType &metaType) { - s << "template<> inline PyTypeObject *SbkType< ::" << metaType.cppSignature() << " >() " + s << "template<> inline PyTypeObject *SbkType< " + << m_gsp << metaType.cppSignature() << " >() " << "{ return " << cpythonTypeNameExt(metaType) << "; }\n"; } diff --git a/sources/shiboken6/generator/shiboken/headergenerator.h b/sources/shiboken6/generator/shiboken/headergenerator.h index e986ff40..03b98e74 100644 --- a/sources/shiboken6/generator/shiboken/headergenerator.h +++ b/sources/shiboken6/generator/shiboken/headergenerator.h @@ -8,8 +8,10 @@ #include "include.h" #include "modifications_typedefs.h" +#include #include +struct IndexValue; class AbstractMetaFunction; struct ModuleHeaderParameters; @@ -30,6 +32,10 @@ protected: private: using InheritedOverloadSet = QSet; + using IndexValues = QList; + + IndexValues collectTypeIndexes(const AbstractMetaClassCList &classList); + IndexValues collectConverterIndexes() const; static void writeCopyCtor(TextStream &s, const AbstractMetaClassCPtr &metaClass); void writeFunction(TextStream &s, @@ -39,10 +45,12 @@ private: static void writeSbkTypeFunction(TextStream &s, const AbstractMetaEnum &cppEnum); static void writeSbkTypeFunction(TextStream &s, const AbstractMetaClassCPtr &cppClass); static void writeSbkTypeFunction(TextStream &s, const AbstractMetaType &metaType); - void writeTypeIndexValueLine(TextStream &s, const ApiExtractorResult &api, - const TypeEntryCPtr &typeEntry); - void writeTypeIndexValueLines(TextStream &s, const ApiExtractorResult &api, - const AbstractMetaClassCPtr &metaClass); + void collectTypeEntryTypeIndexes(const ApiExtractorResult &api, + const TypeEntryCPtr &typeEntry, + IndexValues *indexValues); + void collectClassTypeIndexes(const ApiExtractorResult &api, + const AbstractMetaClassCPtr &metaClass, + IndexValues *indexValues); static void writeProtectedEnumSurrogate(TextStream &s, const AbstractMetaEnum &cppEnum); void writeMemberFunctionWrapper(TextStream &s, const AbstractMetaFunctionCPtr &func, diff --git a/sources/shiboken6/generator/shiboken/overloaddata.cpp b/sources/shiboken6/generator/shiboken/overloaddata.cpp index c1e965fe..c28fcdc1 100644 --- a/sources/shiboken6/generator/shiboken/overloaddata.cpp +++ b/sources/shiboken6/generator/shiboken/overloaddata.cpp @@ -135,10 +135,10 @@ void OverloadDataRootNode::sortNextOverloads(const ApiExtractorResult &api) // Primitive types that are not int, long, short, // char and their respective unsigned counterparts. - static const QStringList nonIntegerPrimitives{floatT(), doubleT(), boolT()}; + static const QStringList nonIntegerPrimitives{floatT, doubleT, boolT}; // Signed integer primitive types. - static const QStringList signedIntegerPrimitives{intT(), shortT(), longT(), longLongT()}; + static const QStringList signedIntegerPrimitives{intT, shortT, longT, longLongT}; // sort the children overloads for (const auto &ov : std::as_const(m_children)) @@ -162,15 +162,15 @@ void OverloadDataRootNode::sortNextOverloads(const ApiExtractorResult &api) it.value().append(ov); } - if (!checkPyObject && typeName == cPyObjectT()) + if (!checkPyObject && typeName == cPyObjectT) checkPyObject = true; - else if (!checkPySequence && typeName == cPySequenceT()) + else if (!checkPySequence && typeName == cPySequenceT) checkPySequence = true; - else if (!checkPyBuffer && typeName == cPyBufferT()) + else if (!checkPyBuffer && typeName == cPyBufferT) checkPyBuffer = true; - else if (!checkQVariant && typeName == qVariantT()) + else if (!checkQVariant && typeName == qVariantT) checkQVariant = true; - else if (!checkQString && typeName == qStringT()) + else if (!checkQString && typeName == qStringT) checkQString = true; for (const auto &instantiation : ov->argType().instantiations()) { @@ -196,9 +196,9 @@ void OverloadDataRootNode::sortNextOverloads(const ApiExtractorResult &api) // Create the graph of type dependencies based on implicit conversions. // All C++ primitive types, add any forgotten type AT THE END OF THIS LIST! - static const QStringList primitiveTypes{intT(), unsignedIntT(), longT(), unsignedLongT(), - shortT(), unsignedShortT(), boolT(), unsignedCharT(), charT(), floatT(), - doubleT(), constCharPtrT()}; + static const QStringList primitiveTypes{intT, unsignedIntT, longT, unsignedLongT, + shortT, unsignedShortT, boolT, unsignedCharT, charT, floatT, + doubleT, constCharPtrT}; QStringList foundPrimitiveTypeIds; for (const auto &p : primitiveTypes) { @@ -207,7 +207,7 @@ void OverloadDataRootNode::sortNextOverloads(const ApiExtractorResult &api) } if (checkPySequence && checkPyObject) - graph.addEdge(cPySequenceT(), cPyObjectT()); + graph.addEdge(cPySequenceT, cPyObjectT); QStringList classesWithIntegerImplicitConversion; @@ -226,7 +226,7 @@ void OverloadDataRootNode::sortNextOverloads(const ApiExtractorResult &api) else convertibleType = getTypeName(function->arguments().constFirst().type()); - if (convertibleType == intT() || convertibleType == unsignedIntT()) + if (convertibleType == intT || convertibleType == unsignedIntT) classesWithIntegerImplicitConversion << targetTypeEntryName; if (!graph.hasNode(convertibleType)) @@ -288,28 +288,28 @@ void OverloadDataRootNode::sortNextOverloads(const ApiExtractorResult &api) if ((checkPySequence || checkPyObject || checkPyBuffer) - && !targetTypeEntryName.contains(cPyObjectT()) - && !targetTypeEntryName.contains(cPyBufferT()) - && !targetTypeEntryName.contains(cPySequenceT())) { + && !targetTypeEntryName.contains(cPyObjectT) + && !targetTypeEntryName.contains(cPyBufferT) + && !targetTypeEntryName.contains(cPySequenceT)) { if (checkPySequence) { // PySequence will be checked after all more specific types, but before PyObject. - graph.addEdge(targetTypeEntryName, cPySequenceT()); + graph.addEdge(targetTypeEntryName, cPySequenceT); } else if (checkPyBuffer) { // PySequence will be checked after all more specific types, but before PyObject. - graph.addEdge(targetTypeEntryName, cPyBufferT()); + graph.addEdge(targetTypeEntryName, cPyBufferT); } else { // Add dependency on PyObject, so its check is the last one (too generic). - graph.addEdge(targetTypeEntryName, cPyObjectT()); + graph.addEdge(targetTypeEntryName, cPyObjectT); } - } else if (checkQVariant && targetTypeEntryName != qVariantT()) { - if (!graph.containsEdge(qVariantT(), targetTypeEntryName)) // Avoid cyclic dependency. - graph.addEdge(targetTypeEntryName, qVariantT()); + } else if (checkQVariant && targetTypeEntryName != qVariantT) { + if (!graph.containsEdge(qVariantT, targetTypeEntryName)) // Avoid cyclic dependency. + graph.addEdge(targetTypeEntryName, qVariantT); } else if (checkQString && ov->argType().isPointer() - && targetTypeEntryName != qStringT() - && targetTypeEntryName != qByteArrayT() - && (!checkPyObject || targetTypeEntryName != cPyObjectT())) { - if (!graph.containsEdge(qStringT(), targetTypeEntryName)) // Avoid cyclic dependency. - graph.addEdge(targetTypeEntryName, qStringT()); + && targetTypeEntryName != qStringT + && targetTypeEntryName != qByteArrayT + && (!checkPyObject || targetTypeEntryName != cPyObjectT)) { + if (!graph.containsEdge(qStringT, targetTypeEntryName)) // Avoid cyclic dependency. + graph.addEdge(targetTypeEntryName, qStringT); } if (targetType.isEnum()) { @@ -320,14 +320,14 @@ void OverloadDataRootNode::sortNextOverloads(const ApiExtractorResult &api) } // QByteArray args need to be checked after QString args - if (graph.hasNode(qStringT()) && graph.hasNode(qByteArrayT())) - graph.addEdge(qStringT(), qByteArrayT()); + if (graph.hasNode(qStringT) && graph.hasNode(qByteArrayT)) + graph.addEdge(qStringT, qByteArrayT); static const Edge rangeOrder[] = - {{doubleT(), floatT()}, - {longLongT(), longT()}, {longLongT(), intT()}, {intT(), shortT()}, - {unsignedLongLongT(), unsignedLongT()}, {unsignedLongLongT(), unsignedT()}, - {unsignedLongLongT(), unsignedIntT()}, {unsignedT(), unsignedShortT()} + {{doubleT, floatT}, + {longLongT, longT}, {longLongT, intT}, {intT, shortT}, + {unsignedLongLongT, unsignedLongT}, {unsignedLongLongT, unsignedT}, + {unsignedLongLongT, unsignedIntT}, {unsignedT, unsignedShortT} }; for (const auto &r : rangeOrder) { if (graph.hasNode(r.first) && graph.hasNode(r.second)) diff --git a/sources/shiboken6/generator/shiboken/pytypenames.h b/sources/shiboken6/generator/shiboken/pytypenames.h index 696e0d88..6c7658ff 100644 --- a/sources/shiboken6/generator/shiboken/pytypenames.h +++ b/sources/shiboken6/generator/shiboken/pytypenames.h @@ -6,24 +6,24 @@ #include -static inline QString pyBoolT() { return QStringLiteral("PyBool"); } -static inline QString pyFloatT() { return QStringLiteral("PyFloat"); } -static inline QString pyLongT() { return QStringLiteral("PyLong"); } -static inline QString pyObjectT() { return QStringLiteral("object"); } -static inline QString pyStrT() { return QStringLiteral("str"); } +constexpr auto pyBoolT = QLatin1StringView ("PyBool"); +constexpr auto pyFloatT = QLatin1StringView ("PyFloat"); +constexpr auto pyLongT = QLatin1StringView ("PyLong"); +constexpr auto pyObjectT = QLatin1StringView ("object"); +constexpr auto pyStrT = QLatin1StringView ("str"); // PYSIDE-1499: A custom type determined by existence of an `__fspath__` attribute. -static inline QString pyPathLikeT() { return QStringLiteral("PyPathLike"); } +constexpr auto pyPathLikeT = QLatin1StringView ("PyPathLike"); -static inline QString cPyBufferT() { return QStringLiteral("PyBuffer"); } -static inline QString cPyListT() { return QStringLiteral("PyList"); } -static inline QString cPyObjectT() { return QStringLiteral("PyObject"); } -static inline QString cPySequenceT() { return QStringLiteral("PySequence"); } -static inline QString cPyTypeObjectT() { return QStringLiteral("PyTypeObject"); } +constexpr auto cPyBufferT = QLatin1StringView ("PyBuffer"); +constexpr auto cPyListT = QLatin1StringView ("PyList"); +constexpr auto cPyObjectT = QLatin1StringView ("PyObject"); +constexpr auto cPySequenceT = QLatin1StringView ("PySequence"); +constexpr auto cPyTypeObjectT = QLatin1StringView ("PyTypeObject"); // numpy -static inline QString cPyArrayObjectT() { return QStringLiteral("PyArrayObject"); } +constexpr auto cPyArrayObjectT = QLatin1StringView ("PyArrayObject"); -static inline QString sbkCharT() { return QStringLiteral("SbkChar"); } +constexpr auto sbkCharT = QLatin1StringView ("SbkChar"); #endif // PYTYPENAMES_H diff --git a/sources/shiboken6/generator/shiboken/shibokengenerator.cpp b/sources/shiboken6/generator/shiboken/shibokengenerator.cpp index 3830540c..a1417e5d 100644 --- a/sources/shiboken6/generator/shiboken/shibokengenerator.cpp +++ b/sources/shiboken6/generator/shiboken/shibokengenerator.cpp @@ -52,47 +52,30 @@ using namespace Qt::StringLiterals; -static const char PARENT_CTOR_HEURISTIC[] = "enable-parent-ctor-heuristic"; -static const char RETURN_VALUE_HEURISTIC[] = "enable-return-value-heuristic"; -static const char DISABLE_VERBOSE_ERROR_MESSAGES[] = "disable-verbose-error-messages"; -static const char USE_ISNULL_AS_NB_BOOL[] = "use-isnull-as-nb-bool"; +static constexpr auto PARENT_CTOR_HEURISTIC = "enable-parent-ctor-heuristic"_L1; +static constexpr auto RETURN_VALUE_HEURISTIC = "enable-return-value-heuristic"_L1; +static constexpr auto DISABLE_VERBOSE_ERROR_MESSAGES = "disable-verbose-error-messages"_L1; +static constexpr auto USE_ISNULL_AS_NB_BOOL = "use-isnull-as-nb-bool"_L1; // FIXME PYSIDE 7: Remove USE_ISNULL_AS_NB_NONZERO/USE_OPERATOR_BOOL_AS_NB_NONZERO -static const char USE_ISNULL_AS_NB_NONZERO[] = "use-isnull-as-nb_nonzero"; -static const char USE_OPERATOR_BOOL_AS_NB_BOOL[] = "use-operator-bool-as-nb-bool"; -static const char USE_OPERATOR_BOOL_AS_NB_NONZERO[] = "use-operator-bool-as-nb-nonzero"; -static const char WRAPPER_DIAGNOSTICS[] = "wrapper-diagnostics"; -static const char NO_IMPLICIT_CONVERSIONS[] = "no-implicit-conversions"; -static const char LEAN_HEADERS[] = "lean-headers"; +static constexpr auto USE_ISNULL_AS_NB_NONZERO = "use-isnull-as-nb_nonzero"_L1; +static constexpr auto USE_OPERATOR_BOOL_AS_NB_BOOL = "use-operator-bool-as-nb-bool"_L1; +static constexpr auto USE_OPERATOR_BOOL_AS_NB_NONZERO = "use-operator-bool-as-nb-nonzero"_L1; +static constexpr auto WRAPPER_DIAGNOSTICS = "wrapper-diagnostics"_L1; +static constexpr auto NO_IMPLICIT_CONVERSIONS = "no-implicit-conversions"_L1; +static constexpr auto LEAN_HEADERS = "lean-headers"_L1; -QString CPP_ARG(int i) +QString CPP_ARG_N(int i) { - return u"cppArg"_s + QString::number(i); + return CPP_ARG + QString::number(i); } -static const QString CPP_ARG_REMOVED_PREFIX = u"removed_cppArg"_s; +constexpr auto CPP_ARG_REMOVED_PREFIX = "removed_cppArg"_L1; QString CPP_ARG_REMOVED(int i) { return CPP_ARG_REMOVED_PREFIX + QString::number(i); } -const QString CPP_RETURN_VAR = u"cppResult"_s; -const QString CPP_SELF_VAR = u"cppSelf"_s; -const QString NULL_PTR = u"nullptr"_s; -const QString PYTHON_ARG = u"pyArg"_s; -const QString PYTHON_ARGS = u"pyArgs"_s; -const QString PYTHON_OVERRIDE_VAR = u"pyOverride"_s; -const QString PYTHON_RETURN_VAR = u"pyResult"_s; -const QString PYTHON_TO_CPP_VAR = u"pythonToCpp"_s; - -const QString CONV_RULE_OUT_VAR_SUFFIX = u"_out"_s; -const QString BEGIN_ALLOW_THREADS = - u"PyThreadState *_save = PyEval_SaveThread(); // Py_BEGIN_ALLOW_THREADS"_s; -const QString END_ALLOW_THREADS = u"PyEval_RestoreThread(_save); // Py_END_ALLOW_THREADS"_s; - -const QString REPR_FUNCTION = u"__repr__"_s; - -const QString CPP_ARG0 = u"cppArg0"_s; const char *const METHOD_DEF_SENTINEL = "{nullptr, nullptr, 0, nullptr} // Sentinel\n"; const char *const PYTHON_TO_CPPCONVERSION_STRUCT = "Shiboken::Conversions::PythonToCppConversion"; @@ -163,32 +146,32 @@ ShibokenGenerator::~ShibokenGenerator() = default; static const QHash &primitiveTypesCorrespondences() { static const QHash result = { - {u"bool"_s, pyBoolT()}, - {u"char"_s, sbkCharT()}, - {u"signed char"_s, sbkCharT()}, - {u"unsigned char"_s, sbkCharT()}, - {intT(), pyLongT()}, - {u"signed int"_s, pyLongT()}, - {u"uint"_s, pyLongT()}, - {u"unsigned int"_s, pyLongT()}, - {shortT(), pyLongT()}, - {u"ushort"_s, pyLongT()}, - {u"signed short"_s, pyLongT()}, - {u"signed short int"_s, pyLongT()}, - {unsignedShortT(), pyLongT()}, - {u"unsigned short int"_s, pyLongT()}, - {longT(), pyLongT()}, - {doubleT(), pyFloatT()}, - {floatT(), pyFloatT()}, - {u"unsigned long"_s, pyLongT()}, - {u"signed long"_s, pyLongT()}, - {u"ulong"_s, pyLongT()}, - {u"unsigned long int"_s, pyLongT()}, - {u"long long"_s, pyLongT()}, - {u"__int64"_s, pyLongT()}, - {u"unsigned long long"_s, pyLongT()}, - {u"unsigned __int64"_s, pyLongT()}, - {u"size_t"_s, pyLongT()} + {u"bool"_s, pyBoolT}, + {u"char"_s, sbkCharT}, + {u"signed char"_s, sbkCharT}, + {u"unsigned char"_s, sbkCharT}, + {intT, pyLongT}, + {u"signed int"_s, pyLongT}, + {u"uint"_s, pyLongT}, + {u"unsigned int"_s, pyLongT}, + {shortT, pyLongT}, + {u"ushort"_s, pyLongT}, + {u"signed short"_s, pyLongT}, + {u"signed short int"_s, pyLongT}, + {unsignedShortT, pyLongT}, + {u"unsigned short int"_s, pyLongT}, + {longT, pyLongT}, + {doubleT, pyFloatT}, + {floatT, pyFloatT}, + {u"unsigned long"_s, pyLongT}, + {u"signed long"_s, pyLongT}, + {u"ulong"_s, pyLongT}, + {u"unsigned long int"_s, pyLongT}, + {u"long long"_s, pyLongT}, + {u"__int64"_s, pyLongT}, + {u"unsigned long long"_s, pyLongT}, + {u"unsigned __int64"_s, pyLongT}, + {u"size_t"_s, pyLongT} }; return result; } @@ -198,18 +181,18 @@ const QHash &ShibokenGenerator::formatUnits() static const QHash result = { {u"char"_s, u'b'}, {u"unsigned char"_s, u'B'}, - {intT(), u'i'}, + {intT, u'i'}, {u"unsigned int"_s, u'I'}, - {shortT(), u'h'}, - {unsignedShortT(), u'H'}, - {longT(), u'l'}, - {unsignedLongLongT(), u'k'}, - {longLongT(), u'L'}, + {shortT, u'h'}, + {unsignedShortT, u'H'}, + {longT, u'l'}, + {unsignedLongLongT, u'k'}, + {longLongT, u'L'}, {u"__int64"_s, u'L'}, - {unsignedLongLongT(), u'K'}, + {unsignedLongLongT, u'K'}, {u"unsigned __int64"_s, u'K'}, - {doubleT(), u'd'}, - {floatT(), u'f'}, + {doubleT, u'd'}, + {floatT, u'f'}, }; return result; } @@ -240,6 +223,15 @@ bool ShibokenGenerator::shouldGenerateCppWrapper(const AbstractMetaClassCPtr &me && wrapper.testFlag(AbstractMetaClass::CppProtectedHackWrapper)); } +bool ShibokenGenerator::shouldGenerateMetaObjectFunctions(const AbstractMetaClassCPtr &metaClass) +{ + return usePySideExtensions() + && (!avoidProtectedHack() || !metaClass->hasPrivateDestructor()) + && !metaClass->typeEntry()->typeFlags() + .testFlag(ComplexTypeEntry::DisableQtMetaObjectFunctions) + && isQObject(metaClass); +} + ShibokenGenerator::FunctionGeneration ShibokenGenerator::functionGeneration( const AbstractMetaFunctionCPtr &func) { @@ -284,7 +276,7 @@ ShibokenGenerator::FunctionGeneration ShibokenGenerator::functionGeneration( // Check on virtuals (including operators). const bool isAbstract = func->isAbstract(); if (!(isAbstract || func->isVirtual()) - || func->attributes().testFlag(AbstractMetaFunction::FinalCppMethod) + || func->cppAttributes().testFlag(FunctionAttribute::Final) || func->isModifiedFinal()) { return result; } @@ -343,7 +335,7 @@ QString ShibokenGenerator::fullPythonClassName(const AbstractMetaClassCPtr &meta fullClassName.prepend(enclosing->name() + u'.'); enclosing = enclosing->enclosingClass(); } - fullClassName.prepend(packageName() + u'.'); + fullClassName.prepend(metaClass->typeEntry()->targetLangPackage() + u'.'); return fullClassName; } @@ -471,13 +463,13 @@ QString ShibokenGenerator::cpythonGetattroFunctionName(const AbstractMetaClassCP QString ShibokenGenerator::cpythonGetterFunctionName(const QString &name, const AbstractMetaClassCPtr &enclosingClass) { - return cpythonBaseName(enclosingClass) + QStringLiteral("_get_") + name; + return cpythonBaseName(enclosingClass) + "_get_"_L1 + name; } QString ShibokenGenerator::cpythonSetterFunctionName(const QString &name, const AbstractMetaClassCPtr &enclosingClass) { - return cpythonBaseName(enclosingClass) + QStringLiteral("_set_") + name; + return cpythonBaseName(enclosingClass) + "_set_"_L1 + name; } QString ShibokenGenerator::cpythonGetterFunctionName(const AbstractMetaField &metaField) @@ -639,7 +631,7 @@ QString ShibokenGenerator::containerCpythonBaseName(const ContainerTypeEntryCPtr default: Q_ASSERT(false); } - return cPySequenceT(); + return cPySequenceT; } QString ShibokenGenerator::cpythonBaseName(const TypeEntryCPtr &type) @@ -659,7 +651,7 @@ QString ShibokenGenerator::cpythonBaseName(const TypeEntryCPtr &type) const auto ctype = std::static_pointer_cast(type); baseName = containerCpythonBaseName(ctype); } else { - baseName = cPyObjectT(); + baseName = cPyObjectT; } return baseName.replace(u"::"_s, u"_"_s); } @@ -674,12 +666,6 @@ QString ShibokenGenerator::cpythonTypeName(const TypeEntryCPtr &type) return cpythonBaseName(type) + u"_TypeF()"_s; } -QString ShibokenGenerator::cpythonTypeNameExt(const TypeEntryCPtr &type) -{ - return cppApiVariableName(type->targetLangPackage()) + u'[' - + getTypeIndexVariableName(type) + u']'; -} - QString ShibokenGenerator::converterObject(const AbstractMetaType &type) { if (type.isCString()) @@ -688,7 +674,7 @@ QString ShibokenGenerator::converterObject(const AbstractMetaType &type) return u"Shiboken::Conversions::PrimitiveTypeConverter()"_s; const AbstractMetaTypeList nestedArrayTypes = type.nestedArrayTypes(); if (!nestedArrayTypes.isEmpty() && nestedArrayTypes.constLast().isCppPrimitive()) { - return QStringLiteral("Shiboken::Conversions::ArrayTypeConverter<") + return "Shiboken::Conversions::ArrayTypeConverter<"_L1 + nestedArrayTypes.constLast().minimalSignature() + u">("_s + QString::number(nestedArrayTypes.size()) + u')'; @@ -735,13 +721,29 @@ QString ShibokenGenerator::converterObject(const TypeEntryCPtr &type) + u'[' + getTypeIndexVariableName(type) + u']'; } -QString ShibokenGenerator::cpythonTypeNameExt(const AbstractMetaType &type) +QString ShibokenGenerator::cpythonTypeNameExtSet(const TypeEntryCPtr &type) +{ + return cppApiVariableName(type->targetLangPackage()) + u'[' + + getTypeIndexVariableName(type) + "].type"_L1; +} + +QString ShibokenGenerator::cpythonTypeNameExtSet(const AbstractMetaType &type) { return cppApiVariableName(type.typeEntry()->targetLangPackage()) + u'[' - + getTypeIndexVariableName(type) + u']'; + + getTypeIndexVariableName(type) + "].type"_L1; } -static inline QString unknownOperator() { return QStringLiteral("__UNKNOWN_OPERATOR__"); } +QString ShibokenGenerator::cpythonTypeNameExt(const TypeEntryCPtr &type) +{ + return "Shiboken::Module::get("_L1 + cppApiVariableName(type->targetLangPackage()) + + u'[' + getTypeIndexVariableName(type) + "])"_L1; +} + +QString ShibokenGenerator::cpythonTypeNameExt(const AbstractMetaType &type) +{ + return u"Shiboken::Module::get("_s + cppApiVariableName(type.typeEntry()->targetLangPackage()) + + u'[' + getTypeIndexVariableName(type) + "])"_L1; +} QString ShibokenGenerator::fixedCppTypeName(const TargetToNativeConversion &toNative) { @@ -791,7 +793,7 @@ QString ShibokenGenerator::pythonOperatorFunctionName(const AbstractMetaFunction QString op = Generator::pythonOperatorFunctionName(func->originalName()); if (op.isEmpty()) { qCWarning(lcShiboken).noquote().nospace() << msgUnknownOperator(func.get()); - return unknownOperator(); + return "__UNKNOWN_OPERATOR__"_L1; } if (func->arguments().isEmpty()) { if (op == u"__sub__") @@ -808,8 +810,8 @@ QString ShibokenGenerator::pythonOperatorFunctionName(const AbstractMetaFunction bool ShibokenGenerator::isNumber(const QString &cpythonApiName) { - return cpythonApiName == pyFloatT() || cpythonApiName == pyLongT() - || cpythonApiName == pyBoolT(); + return cpythonApiName == pyFloatT || cpythonApiName == pyLongT + || cpythonApiName == pyBoolT; } static std::optional @@ -856,7 +858,7 @@ bool ShibokenGenerator::isPyInt(const TypeEntryCPtr &type) if (!cPythonTypeOpt.has_value()) { const auto &mapping = primitiveTypesCorrespondences(); const auto it = mapping.constFind(pte->name()); - return it != mapping.cend() && it.value() == pyLongT(); + return it != mapping.cend() && it.value() == pyLongT; } return cPythonTypeOpt.value() == TypeSystem::CPythonType::Integer; } @@ -1045,8 +1047,8 @@ QString ShibokenGenerator::cpythonToCppConversionFunction(const AbstractMetaType + (type.isPointer() ? u"Pointer"_s : u"Copy"_s) + u'(' + cpythonTypeNameExt(type) + u", "_s; } - return QStringLiteral("Shiboken::Conversions::pythonToCppCopy(%1, ") - .arg(converterObject(type)); + return "Shiboken::Conversions::pythonToCppCopy("_L1 + + converterObject(type) + ", "_L1; } QString ShibokenGenerator::cpythonToPythonConversionFunction(const AbstractMetaType &type) @@ -1145,6 +1147,10 @@ void ShibokenGenerator::writeFunctionArguments(TextStream &s, Options options) const { int argUsed = 0; + if (func->isUserAddedPythonOverride()) { + s << "Shiboken::GilState &gil, PyObject *" << PYTHON_OVERRIDE_VAR; + argUsed += 2; + } for (const auto &arg : func->arguments()) { if (options.testFlag(Generator::SkipRemovedArguments) && arg.isModifiedRemoved()) continue; @@ -1181,6 +1187,8 @@ QString ShibokenGenerator::functionSignature(const AbstractMetaFunctionCPtr &fun { StringStream s(TextStream::Language::Cpp); // The actual function + if (!options.testFlag(Option::SkipDefaultValues) && func->isStatic()) // Declaration + s << "static "; if (func->isEmptyFunction() || func->needsReturnType()) s << functionReturnType(func, options) << ' '; else @@ -1383,7 +1391,7 @@ ShibokenGenerator::ArgumentVarReplacementList } else { argValue = hasConversionRule ? arg.name() + CONV_RULE_OUT_VAR_SUFFIX - : CPP_ARG(argPos); + : CPP_ARG_N(argPos); const auto generatorArg = GeneratorArgument::fromMetaType(type); AbstractMetaType::applyDereference(&argValue, generatorArg.indirections); } @@ -1425,7 +1433,7 @@ void ShibokenGenerator::writeCodeSnips(TextStream &s, static void replacePyArg0(TypeSystem::Language language, QString *code) { - static const QString pyArg0 = u"%PYARG_0"_s; + static constexpr auto pyArg0 = "%PYARG_0"_L1; if (!code->contains(pyArg0)) return; @@ -1464,13 +1472,13 @@ void ShibokenGenerator::writeCodeSnips(TextStream &s, // Replace %PYARG_# variables. replacePyArg0(language, &code); - static const QRegularExpression pyArgsRegex(QStringLiteral("%PYARG_(\\d+)")); + static const QRegularExpression pyArgsRegex("%PYARG_(\\d+)"_L1); Q_ASSERT(pyArgsRegex.isValid()); if (language == TypeSystem::TargetLangCode) { if (usePyArgs) { code.replace(pyArgsRegex, PYTHON_ARGS + u"[\\1-1]"_s); } else { - static const QRegularExpression pyArgsRegexCheck(QStringLiteral("%PYARG_([2-9]+)")); + static const QRegularExpression pyArgsRegexCheck("%PYARG_([2-9]+)"_L1); Q_ASSERT(pyArgsRegexCheck.isValid()); const QRegularExpressionMatch match = pyArgsRegexCheck.match(code); if (match.hasMatch()) { @@ -1483,7 +1491,7 @@ void ShibokenGenerator::writeCodeSnips(TextStream &s, } else { // Replaces the simplest case of attribution to a // Python argument on the binding virtual method. - static const QRegularExpression pyArgsAttributionRegex(QStringLiteral("%PYARG_(\\d+)\\s*=[^=]\\s*([^;]+)")); + static const QRegularExpression pyArgsAttributionRegex("%PYARG_(\\d+)\\s*=[^=]\\s*([^;]+)"_L1); Q_ASSERT(pyArgsAttributionRegex.isValid()); code.replace(pyArgsAttributionRegex, u"PyTuple_SET_ITEM("_s + PYTHON_ARGS + u".object(), \\1-1, \\2)"_s); @@ -1500,7 +1508,7 @@ void ShibokenGenerator::writeCodeSnips(TextStream &s, code.replace(argTypeVar, argTypeVal); } - static const QRegularExpression cppArgTypeRegexCheck(QStringLiteral("%ARG(\\d+)_TYPE")); + static const QRegularExpression cppArgTypeRegexCheck("%ARG(\\d+)_TYPE"_L1); Q_ASSERT(cppArgTypeRegexCheck.isValid()); QRegularExpressionMatchIterator rit = cppArgTypeRegexCheck.globalMatch(code); while (rit.hasNext()) { @@ -1642,8 +1650,8 @@ void ShibokenGenerator::writeCodeSnips(TextStream &s, if (isProtected) { code.replace(u"%TYPE::%FUNCTION_NAME"_s, - QStringLiteral("%1::%2_protected") - .arg(wrapperName(func->ownerClass()), func->originalName())); + wrapperName(func->ownerClass()) + "::"_L1 + + func->originalName() + "_protected"_L1); code.replace(u"%FUNCTION_NAME"_s, func->originalName() + u"_protected"_s); } @@ -1665,7 +1673,7 @@ void ShibokenGenerator::writeCodeSnips(TextStream &s, // and false if it is a variable. static bool isVariable(const QString &code) { - static const QRegularExpression expr(QStringLiteral("^\\s*\\*?\\s*[A-Za-z_][A-Za-z_0-9.]*\\s*(?:\\[[^\\[]+\\])*$")); + static const QRegularExpression expr("^\\s*\\*?\\s*[A-Za-z_][A-Za-z_0-9.]*\\s*(?:\\[[^\\[]+\\])*$"_L1); Q_ASSERT(expr.isValid()); return expr.match(code.trimmed()).hasMatch(); } @@ -1777,7 +1785,7 @@ void ShibokenGenerator::replaceConverterTypeSystemVariable(TypeSystemConverterVa case TypeSystemCheckFunction: conversion = cpythonCheckFunction(conversionType); if (conversionType.typeEntry()->isPrimitive() - && (conversionType.typeEntry()->name() == cPyObjectT() + && (conversionType.typeEntry()->name() == cPyObjectT || !conversion.endsWith(u' '))) { conversion += u'('; break; @@ -1853,7 +1861,7 @@ ShibokenGenerator::AttroCheck ShibokenGenerator::checkAttroFunctionNeeds( FunctionQueryOption::GetAttroFunction)) { result |= AttroCheckFlag::GetattroUser; } - if (usePySideExtensions() && metaClass->qualifiedCppName() == qObjectT()) + if (usePySideExtensions() && metaClass->qualifiedCppName() == qObjectT) result |= AttroCheckFlag::SetattroQObject; if (useOverrideCaching(metaClass)) result |= AttroCheckFlag::SetattroMethodOverride; @@ -1934,17 +1942,17 @@ AbstractMetaClassCPtr QString ShibokenGenerator::getModuleHeaderFileBaseName(const QString &moduleName) { - return moduleCppPrefix(moduleName).toLower() + QStringLiteral("_python"); + return moduleCppPrefix(moduleName).toLower() + "_python"_L1; } QString ShibokenGenerator::getModuleHeaderFileName(const QString &moduleName) { - return getModuleHeaderFileBaseName(moduleName) + QStringLiteral(".h"); + return getModuleHeaderFileBaseName(moduleName) + ".h"_L1; } QString ShibokenGenerator::getPrivateModuleHeaderFileName(const QString &moduleName) { - return getModuleHeaderFileBaseName(moduleName) + QStringLiteral("_p.h"); + return getModuleHeaderFileBaseName(moduleName) + "_p.h"_L1; } IncludeGroupList ShibokenGenerator::classIncludes(const AbstractMetaClassCPtr &metaClass) const @@ -2253,8 +2261,9 @@ ShibokenGenerator::filterGroupedOperatorFunctions(const AbstractMetaClassCPtr &m static bool hidesBaseClassFunctions(const AbstractMetaFunctionCPtr &f) { - return 0 == (f->attributes() - & (AbstractMetaFunction::OverriddenCppMethod | AbstractMetaFunction::FinalCppMethod)); + auto attributes = f->cppAttributes(); + return !attributes.testFlag(FunctionAttribute::Override) + && !attributes.testFlag(FunctionAttribute::Final); } void ShibokenGenerator::getInheritedOverloads(const AbstractMetaClassCPtr &scope, @@ -2330,25 +2339,25 @@ void ShibokenGenerator::getInheritedOverloads(const AbstractMetaClassCPtr &scope QList ShibokenGenerator::options() { return { - {QLatin1StringView(DISABLE_VERBOSE_ERROR_MESSAGES), + {DISABLE_VERBOSE_ERROR_MESSAGES, u"Disable verbose error messages. Turn the python code hard to debug\n" "but safe few kB on the generated bindings."_s}, - {QLatin1StringView(PARENT_CTOR_HEURISTIC), + {PARENT_CTOR_HEURISTIC, u"Enable heuristics to detect parent relationship on constructors."_s}, - {QLatin1StringView(RETURN_VALUE_HEURISTIC), + {RETURN_VALUE_HEURISTIC, u"Enable heuristics to detect parent relationship on return values\n" "(USE WITH CAUTION!)"_s}, - {QLatin1StringView(USE_ISNULL_AS_NB_BOOL), + {USE_ISNULL_AS_NB_BOOL, u"If a class have an isNull() const method, it will be used to compute\n" "the value of boolean casts"_s}, - {QLatin1StringView(LEAN_HEADERS), + {LEAN_HEADERS, u"Forward declare classes in module headers"_s}, - {QLatin1StringView(USE_OPERATOR_BOOL_AS_NB_BOOL), + {USE_OPERATOR_BOOL_AS_NB_BOOL, u"If a class has an operator bool, it will be used to compute\n" "the value of boolean casts"_s}, - {QLatin1StringView(NO_IMPLICIT_CONVERSIONS), + {NO_IMPLICIT_CONVERSIONS, u"Do not generate implicit_conversions for function arguments."_s}, - {QLatin1StringView(WRAPPER_DIAGNOSTICS), + {WRAPPER_DIAGNOSTICS, u"Generate diagnostic code around wrappers"_s} }; } @@ -2368,27 +2377,25 @@ bool ShibokenGeneratorOptionsParser::handleBoolOption(const QString &key, Option { if (source == OptionSource::CommandLineSingleDash) return false; - if (key == QLatin1StringView(PARENT_CTOR_HEURISTIC)) + if (key == PARENT_CTOR_HEURISTIC) return (m_options->useCtorHeuristic = true); - if (key == QLatin1StringView(RETURN_VALUE_HEURISTIC)) + if (key == RETURN_VALUE_HEURISTIC) return (m_options->userReturnValueHeuristic = true); - if (key == QLatin1StringView(DISABLE_VERBOSE_ERROR_MESSAGES)) + if (key == DISABLE_VERBOSE_ERROR_MESSAGES) return (m_options->verboseErrorMessagesDisabled = true); - if (key == QLatin1StringView(USE_ISNULL_AS_NB_BOOL) - || key == QLatin1StringView(USE_ISNULL_AS_NB_NONZERO)) { + if (key == USE_ISNULL_AS_NB_BOOL || key == USE_ISNULL_AS_NB_NONZERO) { return (m_options->useIsNullAsNbBool = true); } - if (key == QLatin1StringView(LEAN_HEADERS)) + if (key == LEAN_HEADERS) return (m_options->leanHeaders= true); - if (key == QLatin1StringView(USE_OPERATOR_BOOL_AS_NB_BOOL) - || key == QLatin1StringView(USE_OPERATOR_BOOL_AS_NB_NONZERO)) { + if (key == USE_OPERATOR_BOOL_AS_NB_BOOL || key == USE_OPERATOR_BOOL_AS_NB_NONZERO) { return (m_options->useOperatorBoolAsNbBool = true); } - if (key == QLatin1StringView(NO_IMPLICIT_CONVERSIONS)) { + if (key == NO_IMPLICIT_CONVERSIONS) { m_options->generateImplicitConversions = false; return true; } - if (key == QLatin1StringView(WRAPPER_DIAGNOSTICS)) + if (key == WRAPPER_DIAGNOSTICS) return (m_options->wrapperDiagnostics = true); return false; } @@ -2440,19 +2447,24 @@ QString ShibokenGenerator::moduleCppPrefix(const QString &moduleName) return result; } +QString ShibokenGenerator::cppApiVariableNameOld(const QString &moduleName) +{ + return "Sbk"_L1 + moduleCppPrefix(moduleName) + "Types"_L1; +} + QString ShibokenGenerator::cppApiVariableName(const QString &moduleName) { - return u"Sbk"_s + moduleCppPrefix(moduleName) + u"Types"_s; + return "Sbk"_L1 + moduleCppPrefix(moduleName) + "TypeStructs"_L1; } QString ShibokenGenerator::pythonModuleObjectName(const QString &moduleName) { - return u"Sbk"_s + moduleCppPrefix(moduleName) + u"ModuleObject"_s; + return "Sbk"_L1 + moduleCppPrefix(moduleName) + "ModuleObject"_L1; } QString ShibokenGenerator::convertersVariableName(const QString &moduleName) { - QString result = cppApiVariableName(moduleName); + QString result = cppApiVariableNameOld(moduleName); result.chop(1); result.append(u"Converters"_s); return result; @@ -2460,11 +2472,11 @@ QString ShibokenGenerator::convertersVariableName(const QString &moduleName) static QString processInstantiationsVariableName(const AbstractMetaType &type) { - QString res = u'_' + _fixedCppTypeName(type.typeEntry()->qualifiedCppName()).toUpper(); + QString res = u'_' + _fixedCppTypeName(type.typeEntry()->qualifiedCppName()); for (const auto &instantiation : type.instantiations()) { res += instantiation.isContainer() ? processInstantiationsVariableName(instantiation) - : u'_' + _fixedCppTypeName(instantiation.cppSignature()).toUpper(); + : u'_' + _fixedCppTypeName(instantiation.cppSignature()); } return res; } @@ -2473,7 +2485,7 @@ static void appendIndexSuffix(QString *s) { if (!s->endsWith(u'_')) s->append(u'_'); - s->append(QStringLiteral("IDX")); + s->append("IDX"_L1); } QString @@ -2482,7 +2494,7 @@ QString const auto templateBaseClass = metaClass->templateBaseClass(); Q_ASSERT(templateBaseClass); QString result = u"SBK_"_s - + _fixedCppTypeName(templateBaseClass->typeEntry()->qualifiedCppName()).toUpper(); + + _fixedCppTypeName(templateBaseClass->typeEntry()->qualifiedCppName()); for (const auto &instantiation : metaClass->templateBaseClassInstantiations()) result += processInstantiationsVariableName(instantiation); appendIndexSuffix(&result); @@ -2504,7 +2516,7 @@ QString ShibokenGenerator::getTypeIndexVariableName(TypeEntryCPtr type) const int dot = package.lastIndexOf(u'.'); result += QStringView{package}.right(package.size() - (dot + 1)); } - result += _fixedCppTypeName(type->qualifiedCppName()).toUpper(); + result += _fixedCppTypeName(type->qualifiedCppName()); appendIndexSuffix(&result); return result; } @@ -2512,12 +2524,45 @@ QString ShibokenGenerator::getTypeIndexVariableName(const AbstractMetaType &type { QString result = u"SBK"_s; if (type.typeEntry()->isContainer()) - result += u'_' + moduleName().toUpper(); + result += u'_' + moduleName(); result += processInstantiationsVariableName(type); appendIndexSuffix(&result); return result; } +void collectfromTypeEntry(TypeEntryCPtr entry, QStringList &typeNames) +{ + if (entry->shouldGenerate()) { + typeNames[entry->sbkIndex()] = entry->qualifiedTargetLangName(); + if (entry->isEnum()) { + auto ete = std::static_pointer_cast(entry); + if (ete->flags()) { + auto entry = ete->flags(); + typeNames[entry->sbkIndex()] = entry->qualifiedTargetLangName(); + } + } + } +} + +void ShibokenGenerator::collectFullTypeNamesArray(QStringList &typeNames) +{ + for (const auto &metaClass : api().classes()) { + collectfromTypeEntry(metaClass->typeEntry(), typeNames); + + for (const AbstractMetaEnum &metaEnum : metaClass->enums()) + collectfromTypeEntry(metaEnum.typeEntry(), typeNames); + + int smartPointerCountIndex = getMaxTypeIndex(); + for (const auto &smp : api().instantiatedSmartPointers()) { + auto entry = smp.type.typeEntry(); + typeNames[smartPointerCountIndex] = entry->qualifiedTargetLangName(); + ++smartPointerCountIndex; + } + } + for (const AbstractMetaEnum &metaEnum : api().globalEnums()) + collectfromTypeEntry(metaEnum.typeEntry(), typeNames); +} + bool ShibokenGenerator::verboseErrorMessagesDisabled() { return m_options.verboseErrorMessagesDisabled; diff --git a/sources/shiboken6/generator/shiboken/shibokengenerator.h b/sources/shiboken6/generator/shiboken/shibokengenerator.h index 25eb7854..22ee73fa 100644 --- a/sources/shiboken6/generator/shiboken/shibokengenerator.h +++ b/sources/shiboken6/generator/shiboken/shibokengenerator.h @@ -203,6 +203,8 @@ protected: /// instead of only a Python wrapper. static bool shouldGenerateCppWrapper(const AbstractMetaClassCPtr &metaClass); + static bool shouldGenerateMetaObjectFunctions(const AbstractMetaClassCPtr &metaClass); + /// Returns which functions need to be generated into the wrapper class static FunctionGeneration functionGeneration(const AbstractMetaFunctionCPtr &func); @@ -250,8 +252,10 @@ protected: static QString cpythonBaseName(const AbstractMetaType &type); static QString cpythonTypeName(const AbstractMetaClassCPtr &metaClass); static QString cpythonTypeName(const TypeEntryCPtr &type); + static QString cpythonTypeNameExtSet(const TypeEntryCPtr &type); + static QString cpythonTypeNameExtSet(const AbstractMetaType &type); static QString cpythonTypeNameExt(const TypeEntryCPtr &type); - static QString cpythonTypeNameExt(const AbstractMetaType &type) ; + static QString cpythonTypeNameExt(const AbstractMetaType &type); static QString cpythonCheckFunction(TypeEntryCPtr type); static QString cpythonCheckFunction(AbstractMetaType metaType); static QString cpythonIsConvertibleFunction(const TypeEntryCPtr &type); @@ -276,8 +280,8 @@ protected: static QString cpythonSetterFunctionName(const QPropertySpec &property, const AbstractMetaClassCPtr &metaClass); static QString cpythonWrapperCPtr(const AbstractMetaClassCPtr &metaClass, - const QString &argName = QStringLiteral("self")); - static QString cpythonWrapperCPtr(const AbstractMetaType &metaType, + const QString &argName = QLatin1StringView("self")); + static QString cpythonWrapperCPtr(const AbstractMetaType &metaType, const QString &argName); static QString cpythonWrapperCPtr(const TypeEntryCPtr &type, const QString &argName); @@ -310,6 +314,7 @@ protected: static bool useOperatorBoolAsNbBool(); /// Generate implicit conversions of function arguments static bool generateImplicitConversions(); + static QString cppApiVariableNameOld(const QString &moduleName = {}); static QString cppApiVariableName(const QString &moduleName = QString()); static QString pythonModuleObjectName(const QString &moduleName = QString()); static QString convertersVariableName(const QString &moduleName = QString()); @@ -321,6 +326,9 @@ protected: static QString getTypeIndexVariableName(TypeEntryCPtr type); static QString getTypeIndexVariableName(const AbstractMetaType &type) ; + /// Collect all type names as an array for initializing the type/name struct. + void collectFullTypeNamesArray(QStringList &typeNames); + /// Returns true if the user don't want verbose error messages on the generated bindings. static bool verboseErrorMessagesDisabled(); diff --git a/sources/shiboken6/generators/shiboken/shiboken.cpp b/sources/shiboken6/generators/shiboken/shiboken.cpp deleted file mode 100644 index 193b8e90..00000000 --- a/sources/shiboken6/generators/shiboken/shiboken.cpp +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "cppgenerator.h" -#include "headergenerator.h" - -EXPORT_GENERATOR_PLUGIN(new CppGenerator << new HeaderGenerator) diff --git a/sources/shiboken6/libshiboken/CMakeLists.txt b/sources/shiboken6/libshiboken/CMakeLists.txt index 4638ca6f..b5bbb498 100644 --- a/sources/shiboken6/libshiboken/CMakeLists.txt +++ b/sources/shiboken6/libshiboken/CMakeLists.txt @@ -66,6 +66,7 @@ debugfreehook.cpp debugfreehook.h gilstate.cpp gilstate.h helper.cpp helper.h pep384impl.cpp pep384impl.h +pyobjectholder.h sbkarrayconverter.cpp sbkarrayconverter.h sbkarrayconverter_p.h sbkcontainer.cpp sbkcontainer.h sbkconverter.cpp sbkconverter.h sbkconverter_p.h @@ -158,6 +159,7 @@ install(FILES bindingmanager.h gilstate.h helper.h + pyobjectholder.h sbkarrayconverter.h sbkcontainer.h sbkconverter.h @@ -180,6 +182,7 @@ install(FILES sbkpython.h sbkwindows.h pep384impl.h + pep384ext.h voidptr.h bufferprocs_py37.h "${CMAKE_CURRENT_BINARY_DIR}/sbkversion.h" diff --git a/sources/shiboken6/libshiboken/autodecref.h b/sources/shiboken6/libshiboken/autodecref.h index f1b48fbd..62a8584e 100644 --- a/sources/shiboken6/libshiboken/autodecref.h +++ b/sources/shiboken6/libshiboken/autodecref.h @@ -5,7 +5,6 @@ #define AUTODECREF_H #include "sbkpython.h" -#include "basewrapper.h" #include @@ -16,13 +15,13 @@ namespace Shiboken /** * AutoDecRef holds a PyObject pointer and decrement its reference counter when destroyed. */ -struct LIBSHIBOKEN_API AutoDecRef +struct AutoDecRef { public: AutoDecRef(const AutoDecRef &) = delete; AutoDecRef(AutoDecRef &&o) noexcept : m_pyObj{std::exchange(o.m_pyObj, nullptr)} {} AutoDecRef &operator=(const AutoDecRef &) = delete; - AutoDecRef &operator=(AutoDecRef &&o) + AutoDecRef &operator=(AutoDecRef &&o) noexcept { m_pyObj = std::exchange(o.m_pyObj, nullptr); return *this; @@ -44,10 +43,10 @@ public: Py_XDECREF(m_pyObj); } - inline bool isNull() const { return m_pyObj == nullptr; } + [[nodiscard]] bool isNull() const { return m_pyObj == nullptr; } /// Returns the pointer of the Python object being held. - inline PyObject *object() { return m_pyObj; } - inline operator PyObject *() { return m_pyObj; } + [[nodiscard]] PyObject *object() const { return m_pyObj; } + [[nodiscard]] operator PyObject *() const { return m_pyObj; } #ifndef Py_LIMITED_API [[deprecated]] inline operator PyTupleObject *() { return reinterpret_cast(m_pyObj); } @@ -86,4 +85,3 @@ private: } // namespace Shiboken #endif // AUTODECREF_H - diff --git a/sources/shiboken6/libshiboken/basewrapper.cpp b/sources/shiboken6/libshiboken/basewrapper.cpp index 70e1e2f4..ffa73394 100644 --- a/sources/shiboken6/libshiboken/basewrapper.cpp +++ b/sources/shiboken6/libshiboken/basewrapper.cpp @@ -5,10 +5,12 @@ #include "basewrapper_p.h" #include "bindingmanager.h" #include "helper.h" +#include "pep384ext.h" #include "sbkconverter.h" #include "sbkenum.h" #include "sbkerrors.h" #include "sbkfeature_base.h" +#include "sbkmodule.h" #include "sbkstring.h" #include "sbkstaticstrings.h" #include "sbkstaticstrings_p.h" @@ -25,7 +27,9 @@ #include "signature_p.h" #include "voidptr.h" +#include #include +#include #if defined(__APPLE__) #include @@ -55,7 +59,7 @@ void Sbk_object_dealloc(PyObject *self) // This was not needed before Python 3.8 (Python issue 35810) Py_DECREF(Py_TYPE(self)); } - Py_TYPE(self)->tp_free(self); + PepExt_TypeCallFree(self); } static void SbkObjectType_tp_dealloc(PyTypeObject *pyType); @@ -152,11 +156,21 @@ static PyTypeObject *createObjectTypeType() "1:Shiboken.ObjectType", static_cast(PyType_Type.tp_basicsize) + 1, // see above 0, // sizeof(PyMemberDef), not for PyPy without a __len__ defined - Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_TYPE_SUBCLASS, + SbkObjectType_Type_slots, + }; + + PyType_Spec SbkObjectType_Type_spec_312 = { + "1:Shiboken.ObjectType", + -long(sizeof(SbkObjectTypePrivate)), + 0, // sizeof(PyMemberDef), not for PyPy without a __len__ defined + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_TYPE_SUBCLASS, SbkObjectType_Type_slots, }; - return SbkType_FromSpec(&SbkObjectType_Type_spec); + return SbkType_FromSpec(_PepRuntimeVersion() >= 0x030C00 ? + &SbkObjectType_Type_spec_312 : + &SbkObjectType_Type_spec); } PyTypeObject *SbkObjectType_TypeF(void) @@ -198,10 +212,8 @@ static int SbkObject_tp_traverse(PyObject *self, visitproc visit, void *arg) if (sbkSelf->ob_dict) Py_VISIT(sbkSelf->ob_dict); -#if PY_VERSION_HEX >= 0x03090000 // This was not needed before Python 3.9 (Python issue 35810 and 40217) Py_VISIT(Py_TYPE(self)); -#endif return 0; } @@ -249,12 +261,13 @@ static PyTypeObject *createObjectType() // But before 3.12 is the minimum version, we cannot use the new // function, although we would need this for 3.12 :-D // We do a special patching here that is triggered through Py_None. - return SbkType_FromSpec_BMDWB(&SbkObject_Type_spec, - Py_None, // bases, special flag! - SbkObjectType_TypeF(), - offsetof(SbkObject, ob_dict), - offsetof(SbkObject, weakreflist), - nullptr); // bufferprocs + auto *type = SbkType_FromSpec_BMDWB(&SbkObject_Type_spec, + Py_None, // bases, spectial flag! + SbkObjectType_TypeF(), + offsetof(SbkObject, ob_dict), + offsetof(SbkObject, weakreflist), + nullptr); // bufferprocs + return type; } PyTypeObject *SbkObject_TypeF(void) @@ -509,7 +522,7 @@ static PyTypeObject *SbkObjectType_tp_new(PyTypeObject *metatype, PyObject *args for (int i=0, i_max=PyTuple_GET_SIZE(pyBases); i < i_max; i++) { PyObject *baseType = PyTuple_GET_ITEM(pyBases, i); - if (reinterpret_cast(baseType)->tp_new == SbkDummyNew) { + if (PepExt_Type_GetNewSlot(reinterpret_cast(baseType)) == SbkDummyNew) { // PYSIDE-595: A base class does not allow inheritance. return reinterpret_cast(SbkDummyNew(metatype, args, kwds)); } @@ -661,10 +674,8 @@ PyObject *FallbackRichCompare(PyObject *self, PyObject *other, int op) bool SbkObjectType_Check(PyTypeObject *type) { - static auto *obMeta = reinterpret_cast(SbkObjectType_TypeF()); - auto *obType = reinterpret_cast(type); - return obMeta == reinterpret_cast(Py_TYPE(obType)) - || PyObject_IsInstance(obType, obMeta); + static auto *meta = SbkObjectType_TypeF(); + return Py_TYPE(type) == meta || PyType_IsSubtype(Py_TYPE(type), meta); } } //extern "C" @@ -739,6 +750,19 @@ bool DtorAccumulatorVisitor::visit(PyTypeObject *node) void _initMainThreadId(); // helper.cpp +static std::string msgFailedToInitializeType(const char *description) +{ + std::ostringstream stream; + stream << "[libshiboken] Failed to initialize " << description; + if (auto *error = PepErr_GetRaisedException()) { + if (auto *str = PyObject_Str(error)) + stream << ": " << Shiboken::String::toCString(str); + Py_DECREF(error); + } + stream << '.'; + return stream.str(); +} + namespace Conversions { void init(); } void init() @@ -754,11 +778,13 @@ void init() //Init private data Pep384_Init(); - if (PyType_Ready(SbkObjectType_TypeF()) < 0) - Py_FatalError("[libshiboken] Failed to initialize Shiboken.BaseWrapperType metatype."); + auto *type = SbkObjectType_TypeF(); + if (type == nullptr || PyType_Ready(type) < 0) + Py_FatalError(msgFailedToInitializeType("Shiboken.BaseWrapperType metatype").c_str()); - if (PyType_Ready(SbkObject_TypeF()) < 0) - Py_FatalError("[libshiboken] Failed to initialize Shiboken.BaseWrapper type."); + type = SbkObject_TypeF(); + if (type == nullptr || PyType_Ready(type) < 0) + Py_FatalError(msgFailedToInitializeType("Shiboken.BaseWrapper type").c_str()); VoidPtr::init(); @@ -959,24 +985,19 @@ introduceWrapperType(PyObject *enclosingObject, const char *originalName, PyType_Spec *typeSpec, ObjectDestructor cppObjDtor, - PyTypeObject *baseType, - PyObject *baseTypes, + PyObject *bases, unsigned wrapperFlags) { - auto *base = baseType ? baseType : SbkObject_TypeF(); - typeSpec->slots[0].pfunc = reinterpret_cast(base); - auto *bases = baseTypes ? baseTypes : PyTuple_Pack(1, base); + assert(PySequence_Fast_GET_SIZE(bases) > 0); + typeSpec->slots[0].pfunc = PySequence_Fast_GET_ITEM(bases, 0); auto *type = SbkType_FromSpecBasesMeta(typeSpec, bases, SbkObjectType_TypeF()); - for (int i = 0; i < PySequence_Fast_GET_SIZE(bases); ++i) { - auto *st = reinterpret_cast(PySequence_Fast_GET_ITEM(bases, i)); - BindingManager::instance().addClassInheritance(st, type); - } - auto sotp = PepType_SOTP(type); if (wrapperFlags & DeleteInMainThread) sotp->delete_in_main_thread = 1; + sotp->type_behaviour = (wrapperFlags & Value) != 0 + ? BEHAVIOUR_VALUETYPE : BEHAVIOUR_OBJECTTYPE; setOriginalName(type, originalName); setDestructorFunction(type, cppObjDtor); @@ -1006,16 +1027,19 @@ introduceWrapperType(PyObject *enclosingObject, void setSubTypeInitHook(PyTypeObject *type, SubTypeInitHook func) { + assert(SbkObjectType_Check(type)); PepType_SOTP(type)->subtype_init = func; } void *getTypeUserData(PyTypeObject *type) { + assert(SbkObjectType_Check(type)); return PepType_SOTP(type)->user_data; } void setTypeUserData(PyTypeObject *type, void *userData, DeleteUserDataFunc d_func) { + assert(SbkObjectType_Check(type)); auto *sotp = PepType_SOTP(type); sotp->user_data = userData; sotp->d_func = d_func; @@ -1417,10 +1441,15 @@ PyObject *newObject(PyTypeObject *instanceType, { // Try to find the exact type of cptr. if (!isExactType) { - if (PyTypeObject *exactType = ObjectType::typeForTypeName(typeName)) + if (PyTypeObject *exactType = ObjectType::typeForTypeName(typeName)) { instanceType = exactType; - else - instanceType = BindingManager::instance().resolveType(&cptr, instanceType); + } else { + auto resolved = BindingManager::instance().findDerivedType(cptr, instanceType); + if (resolved.first != nullptr) { + instanceType = resolved.first; + cptr = resolved.second; + } + } } bool shouldCreate = true; @@ -1631,7 +1660,7 @@ void deallocData(SbkObject *self, bool cleanup) } delete self->d; // PYSIDE-205: always delete d. Py_XDECREF(self->ob_dict); - Py_TYPE(self)->tp_free(self); + PepExt_TypeCallFree(reinterpret_cast(self)); } void setTypeUserData(SbkObject *wrapper, void *userData, DeleteUserDataFunc d_func) @@ -1723,6 +1752,11 @@ static std::vector getBases(SbkObject *self) : std::vector(1, Py_TYPE(self)); } +static bool isValueType(SbkObject *self) +{ + return PepType_SOTP(Py_TYPE(self))->type_behaviour == BEHAVIOUR_VALUETYPE; +} + void _debugFormat(std::ostream &s, SbkObject *self) { assert(self); @@ -1746,6 +1780,8 @@ void _debugFormat(std::ostream &s, SbkObject *self) s << " [validCppObject]"; if (d->cppObjectCreated) s << " [wasCreatedByPython]"; + s << (isValueType(self) ? " [value]" : " [object]"); + if (d->parentInfo) { if (auto *parent = d->parentInfo->parent) s << ", parent=" << reinterpret_cast(parent)->ob_type->tp_name @@ -1776,8 +1812,9 @@ std::string info(SbkObject *self) s << "hasOwnership...... " << bool(self->d->hasOwnership) << "\n" "containsCppWrapper " << self->d->containsCppWrapper << "\n" "validCppObject.... " << self->d->validCppObject << "\n" - "wasCreatedByPython " << self->d->cppObjectCreated << "\n"; - + "wasCreatedByPython " << self->d->cppObjectCreated << "\n" + "value...... " << isValueType(self) << "\n" + "reference count... " << reinterpret_cast(self)->ob_refcnt << '\n'; if (self->d->parentInfo && self->d->parentInfo->parent) { s << "parent............ "; diff --git a/sources/shiboken6/libshiboken/basewrapper.h b/sources/shiboken6/libshiboken/basewrapper.h index 90ec8cb4..4835c481 100644 --- a/sources/shiboken6/libshiboken/basewrapper.h +++ b/sources/shiboken6/libshiboken/basewrapper.h @@ -59,7 +59,9 @@ using SubTypeInitHook = void (*)(PyTypeObject *, PyObject *, PyObject *); /// PYSIDE-1019: Set the function to select the current feature. /// Return value is the previous content. using SelectableFeatureHook = void (*)(PyTypeObject *); +using SelectableFeatureCallback = void (*)(bool); LIBSHIBOKEN_API SelectableFeatureHook initSelectableFeature(SelectableFeatureHook func); +LIBSHIBOKEN_API void setSelectableFeatureCallback(SelectableFeatureCallback func); /// PYSIDE-1626: Enforcing a context switch without further action. LIBSHIBOKEN_API void SbkObjectType_UpdateFeature(PyTypeObject *type); @@ -198,14 +200,15 @@ LIBSHIBOKEN_API const char *getOriginalName(PyTypeObject *self); LIBSHIBOKEN_API void setTypeDiscoveryFunctionV2(PyTypeObject *self, TypeDiscoveryFuncV2 func); LIBSHIBOKEN_API void copyMultipleInheritance(PyTypeObject *self, PyTypeObject *other); LIBSHIBOKEN_API void setMultipleInheritanceFunction(PyTypeObject *self, MultipleInheritanceInitFunction func); -LIBSHIBOKEN_API MultipleInheritanceInitFunction getMultipleInheritanceFunction(PyTypeObject *self); +LIBSHIBOKEN_API MultipleInheritanceInitFunction getMultipleInheritanceFunction(PyTypeObject *type); LIBSHIBOKEN_API void setDestructorFunction(PyTypeObject *self, ObjectDestructor func); enum WrapperFlags { InnerClass = 0x1, - DeleteInMainThread = 0x2 + DeleteInMainThread = 0x2, + Value = 0x4 }; /** @@ -226,13 +229,12 @@ enum WrapperFlags * \returns true if the initialization went fine, false otherwise. */ LIBSHIBOKEN_API PyTypeObject *introduceWrapperType(PyObject *enclosingObject, - const char *typeName, - const char *originalName, - PyType_Spec *typeSpec, - ObjectDestructor cppObjDtor, - PyTypeObject *baseType, - PyObject *baseTypes, - unsigned wrapperFlags = 0); + const char *typeName, + const char *originalName, + PyType_Spec *typeSpec, + ObjectDestructor cppObjDtor, + PyObject *bases, + unsigned wrapperFlags = 0); /** * Set the subtype init hook for a type. diff --git a/sources/shiboken6/libshiboken/bindingmanager.cpp b/sources/shiboken6/libshiboken/bindingmanager.cpp index a9b87f7f..83c927ae 100644 --- a/sources/shiboken6/libshiboken/bindingmanager.cpp +++ b/sources/shiboken6/libshiboken/bindingmanager.cpp @@ -7,6 +7,7 @@ #include "bindingmanager.h" #include "gilstate.h" #include "helper.h" +#include "sbkmodule.h" #include "sbkstring.h" #include "sbkstaticstrings.h" #include "sbkfeature_base.h" @@ -15,94 +16,152 @@ #include #include #include +#include #include +#include #include +#include + +// GraphNode for the dependency graph. It keeps a pointer to +// the TypeInitStruct to be able to lazily create the type and hashes +// by the full type name. +struct GraphNode +{ + explicit GraphNode(Shiboken::Module::TypeInitStruct *i) : name(i->fullName), initStruct(i) {} + explicit GraphNode(const char *n) : name(n), initStruct(nullptr) {} // Only for searching + + std::string_view name; + Shiboken::Module::TypeInitStruct *initStruct; + + friend bool operator==(const GraphNode &n1, const GraphNode &n2) { return n1.name == n2.name; } + friend bool operator!=(const GraphNode &n1, const GraphNode &n2) { return n1.name != n2.name; } +}; + +template <> +struct std::hash { + size_t operator()(const GraphNode &n) const noexcept + { + return std::hash{}(n.name); + } +}; namespace Shiboken { using WrapperMap = std::unordered_map; -class Graph +template +class BaseGraph { public: - using NodeList = std::vector; - using Edges = std::unordered_map; + using NodeList = std::vector; + using NodeSet = std::unordered_set; + + using Edges = std::unordered_map; Edges m_edges; - Graph() = default; + BaseGraph() = default; - void addEdge(PyTypeObject *from, PyTypeObject *to) + void addEdge(NodeType from, NodeType to) { m_edges[from].push_back(to); } -#ifndef NDEBUG - void dumpDotGraph() const + NodeSet nodeSet() const { - std::ofstream file("/tmp/shiboken_graph.dot"); - - file << "digraph D {\n"; - + NodeSet result; for (const auto &p : m_edges) { - auto *node1 = p.first; - const NodeList &nodeList = p.second; - for (const PyTypeObject *o : nodeList) { - auto *node2 = o; - file << '"' << node2->tp_name << "\" -> \"" - << node1->tp_name << "\"\n"; - } + result.insert(p.first); + for (const auto node2 : p.second) + result.insert(node2); } - file << "}\n"; + return result; } -#endif +}; + +class Graph : public BaseGraph +{ +public: + using TypeCptrPair = BindingManager::TypeCptrPair; - PyTypeObject *identifyType(void **cptr, PyTypeObject *type, PyTypeObject *baseType) const + TypeCptrPair identifyType(void *cptr, PyTypeObject *type, PyTypeObject *baseType) const { - auto edgesIt = m_edges.find(type); - if (edgesIt != m_edges.end()) { - const NodeList &adjNodes = m_edges.find(type)->second; - for (PyTypeObject *node : adjNodes) { - PyTypeObject *newType = identifyType(cptr, node, baseType); - if (newType) - return newType; - } - } - void *typeFound = nullptr; - auto *sotp = PepType_SOTP(type); - if (sotp->type_discovery) - typeFound = sotp->type_discovery(*cptr, baseType); - if (typeFound) { - // This "typeFound != type" is needed for backwards compatibility with old modules using a newer version of - // libshiboken because old versions of type_discovery function used to return a PyTypeObject *instead of - // a possible variation of the C++ instance pointer (*cptr). - if (typeFound != type) - *cptr = typeFound; - return type; - } - return nullptr; + return identifyType(cptr, GraphNode(type->tp_name), type, baseType); } -}; + bool dumpTypeGraph(const char *fileName) const; -#ifndef NDEBUG -static void showWrapperMap(const WrapperMap &wrapperMap) +private: + TypeCptrPair identifyType(void *cptr, const GraphNode &typeNode, PyTypeObject *type, + PyTypeObject *baseType) const; +}; + +Graph::TypeCptrPair Graph::identifyType(void *cptr, + const GraphNode &typeNode, PyTypeObject *type, + PyTypeObject *baseType) const { - if (Shiboken::pyVerbose() > 0) { - fprintf(stderr, "-------------------------------\n"); - fprintf(stderr, "WrapperMap: %p (size: %d)\n", &wrapperMap, (int) wrapperMap.size()); - for (auto it = wrapperMap.begin(), end = wrapperMap.end(); it != end; ++it) { - const SbkObject *sbkObj = it->second; - fprintf(stderr, "key: %p, value: %p (%s, refcnt: %d)\n", it->first, - static_cast(sbkObj), - (Py_TYPE(sbkObj))->tp_name, - int(Py_REFCNT(reinterpret_cast(sbkObj)))); + assert(typeNode.initStruct != nullptr || type != nullptr); + auto edgesIt = m_edges.find(typeNode); + if (edgesIt != m_edges.end()) { + const NodeList &adjNodes = edgesIt->second; + for (const auto &node : adjNodes) { + auto newType = identifyType(cptr, node, nullptr, baseType); + if (newType.first != nullptr) + return newType; } - fprintf(stderr, "-------------------------------\n"); } + + if (type == nullptr) { + if (typeNode.initStruct->type == nullptr) // Layzily create type + type = Shiboken::Module::get(*typeNode.initStruct); + else + type = typeNode.initStruct->type; + } + + auto *sotp = PepType_SOTP(type); + if (sotp->type_discovery != nullptr) { + if (void *derivedCPtr = sotp->type_discovery(cptr, baseType)) + return {type, derivedCPtr}; + } + return {nullptr, nullptr}; +} + +static void formatDotNode(std::string_view name, std::ostream &file) +{ + auto lastDot = name.rfind('.'); + file << " \"" << name << "\" [ label="; + if (lastDot != std::string::npos) { + file << '"' << name.substr(lastDot + 1) << "\" tooltip=\"" + << name.substr(0, lastDot) << '"'; + } else { + file << '"' << name << '"'; + } + file << " ]\n"; +} + +bool Graph::dumpTypeGraph(const char *fileName) const +{ + std::ofstream file(fileName); + if (!file.good()) + return false; + + file << "digraph D {\n"; + + // Define nodes with short names + for (const auto &node : nodeSet()) + formatDotNode(node.name, file); + + // Write edges + for (const auto &p : m_edges) { + const auto &node1 = p.first; + const NodeList &nodeList = p.second; + for (const auto &node2 : nodeList) + file << " \"" << node2.name << "\" -> \"" << node1.name << "\"\n"; + } + file << "}\n"; + return true; } -#endif struct BindingManager::BindingManagerPrivate { using DestructorEntries = std::vector; @@ -115,9 +174,6 @@ struct BindingManager::BindingManagerPrivate { std::recursive_mutex wrapperMapLock; Graph classHierarchy; DestructorEntries deleteInMainThread; - bool destroying; - - BindingManagerPrivate() : destroying(false) {} bool releaseWrapper(void *cptr, SbkObject *wrapper, const int *bases = nullptr); bool releaseWrapperHelper(void *cptr, SbkObject *wrapper); @@ -189,7 +245,8 @@ BindingManager::~BindingManager() debugRemoveFreeHook(); #endif #ifndef NDEBUG - showWrapperMap(m_d->wrapperMapper); + if (Shiboken::pyVerbose() > 0) + dumpWrapperMap(); #endif /* Cleanup hanging references. We just invalidate them as when * the BindingManager is being destroyed the interpreter is alredy @@ -363,15 +420,24 @@ PyObject *BindingManager::getOverride(const void *cptr, return nullptr; } -void BindingManager::addClassInheritance(PyTypeObject *parent, PyTypeObject *child) +void BindingManager::addClassInheritance(Module::TypeInitStruct *parent, + Module::TypeInitStruct *child) { - m_d->classHierarchy.addEdge(parent, child); + m_d->classHierarchy.addEdge(GraphNode(parent), GraphNode(child)); } +BindingManager::TypeCptrPair BindingManager::findDerivedType(void *cptr, PyTypeObject *type) const +{ + return m_d->classHierarchy.identifyType(cptr, type, type); +} + +// FIXME PYSIDE7: remove, just for compatibility PyTypeObject *BindingManager::resolveType(void **cptr, PyTypeObject *type) { - PyTypeObject *identifiedType = m_d->classHierarchy.identifyType(cptr, type, type); - return identifiedType ? identifiedType : type; + auto result = findDerivedType(*cptr, type); + if (result.second != nullptr) + *cptr = result.second; + return result.first != nullptr ? result.first : type; } std::set BindingManager::getAllPyObjects() @@ -395,6 +461,27 @@ void BindingManager::visitAllPyObjects(ObjectVisitor visitor, void *data) } } +bool BindingManager::dumpTypeGraph(const char *fileName) const +{ + return m_d->classHierarchy.dumpTypeGraph(fileName); +} + +void BindingManager::dumpWrapperMap() +{ + const auto &wrapperMap = m_d->wrapperMapper; + std::cerr << "-------------------------------\n" + << "WrapperMap size: " << wrapperMap.size() << " Types: " + << m_d->classHierarchy.nodeSet().size() << '\n'; + for (auto it = wrapperMap.begin(), end = wrapperMap.end(); it != end; ++it) { + const SbkObject *sbkObj = it->second; + std::cerr << "key: " << it->first << ", value: " + << static_cast(sbkObj) << " (" + << (Py_TYPE(sbkObj))->tp_name << ", refcnt: " + << Py_REFCNT(reinterpret_cast(sbkObj)) << ")\n"; + } + std::cerr << "-------------------------------\n"; +} + static bool isPythonType(PyTypeObject *type) { // This is a type which should be called by multiple inheritance. @@ -408,6 +495,8 @@ bool callInheritedInit(PyObject *self, PyObject *args, PyObject *kwds, using Shiboken::AutoDecRef; static PyObject *const _init = String::createStaticString("__init__"); + static PyObject *objectInit = + PyObject_GetAttr(reinterpret_cast(&PyBaseObject_Type), _init); // A native C++ self cannot have multiple inheritance. if (!Object::isUserType(self)) @@ -441,6 +530,10 @@ bool callInheritedInit(PyObject *self, PyObject *args, PyObject *kwds, if (subType == &PyBaseObject_Type) return false; AutoDecRef func(PyObject_GetAttr(obSubType, _init)); + // PYSIDE-2654: If this has no implementation then we get object.__init__ + // but that is the same case like above. + if (func == objectInit) + return false; // PYSIDE-2294: We need to explicitly ignore positional args in a mixin class. SBK_UNUSED(args); AutoDecRef newArgs(PyTuple_New(1)); diff --git a/sources/shiboken6/libshiboken/bindingmanager.h b/sources/shiboken6/libshiboken/bindingmanager.h index 4b21ae83..54c4e486 100644 --- a/sources/shiboken6/libshiboken/bindingmanager.h +++ b/sources/shiboken6/libshiboken/bindingmanager.h @@ -5,14 +5,20 @@ #define BINDINGMANAGER_H #include "sbkpython.h" -#include #include "shibokenmacros.h" +#include +#include + struct SbkObject; namespace Shiboken { +namespace Module { +struct TypeInitStruct; +} + struct DestructorEntry; using ObjectVisitor = void (*)(SbkObject *, void *); @@ -38,7 +44,15 @@ public: SbkObject *retrieveWrapper(const void *cptr); PyObject *getOverride(const void *cptr, PyObject *nameCache[], const char *methodName); - void addClassInheritance(PyTypeObject *parent, PyTypeObject *child); + void addClassInheritance(Module::TypeInitStruct *parent, Module::TypeInitStruct *child); + /// Try to find the correct type of cptr via type discovery knowing that it's at least + /// of type \p type. If a derived class is found, it returns a cptr cast to the type + /// (which may be different in case of multiple inheritance. + /// \param cptr a pointer to the instance of type \p type + /// \param type type of cptr + using TypeCptrPair = std::pair; + TypeCptrPair findDerivedType(void *cptr, PyTypeObject *type) const; + /** * Try to find the correct type of *cptr knowing that it's at least of type \p type. * In case of multiple inheritance this function may change the contents of cptr. @@ -46,7 +60,7 @@ public: * \param type type of *cptr * \warning This function is slow, use it only as last resort. */ - PyTypeObject *resolveType(void **cptr, PyTypeObject *type); + [[deprecated]] PyTypeObject *resolveType(void **cptr, PyTypeObject *type); std::set getAllPyObjects(); @@ -59,6 +73,9 @@ public: */ void visitAllPyObjects(ObjectVisitor visitor, void *data); + bool dumpTypeGraph(const char *fileName) const; + void dumpWrapperMap(); + private: ~BindingManager(); BindingManager(); diff --git a/sources/shiboken6/libshiboken/helper.cpp b/sources/shiboken6/libshiboken/helper.cpp index 9c75c00d..46af6895 100644 --- a/sources/shiboken6/libshiboken/helper.cpp +++ b/sources/shiboken6/libshiboken/helper.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include #include @@ -43,14 +44,21 @@ static std::optional getIntAttr(PyObject *obj, const char *what) return std::nullopt; } +static bool verbose = false; + static void formatTypeTuple(PyObject *t, const char *what, std::ostream &str); static void formatPyTypeObject(const PyTypeObject *obj, std::ostream &str, bool verbose) { - if (obj) { + if (obj == nullptr) { + str << '0'; + return; + } + + str << '"' << obj->tp_name << '"'; + if (verbose) { bool immutableType = false; - str << '"' << obj->tp_name << "\", 0x" << std::hex - << obj->tp_flags << std::dec; + str << ", 0x" << std::hex << obj->tp_flags << std::dec; if (obj->tp_flags & Py_TPFLAGS_HEAPTYPE) str << " [heaptype]"; if (obj->tp_flags & Py_TPFLAGS_BASETYPE) @@ -79,7 +87,6 @@ static void formatPyTypeObject(const PyTypeObject *obj, std::ostream &str, bool str << " [readying]"; if (obj->tp_flags & Py_TPFLAGS_METHOD_DESCRIPTOR) str << " [method_descriptor]"; -#if PY_VERSION_HEX >= 0x03090000 # ifndef Py_LIMITED_API if (obj->tp_flags & Py_TPFLAGS_HAVE_VECTORCALL) str << " [vectorcall]"; @@ -97,7 +104,6 @@ static void formatPyTypeObject(const PyTypeObject *obj, std::ostream &str, bool str << " [sequence]"; # endif // !Py_LIMITED_API # endif // 3.10 -#endif // 3.9 if (obj->tp_basicsize != 0) str << ", basicsize=" << obj->tp_basicsize; if (verbose) { @@ -110,8 +116,6 @@ static void formatPyTypeObject(const PyTypeObject *obj, std::ostream &str, bool } } } - } else { - str << '0'; } } @@ -208,6 +212,8 @@ static void formatPyUnicode(PyObject *obj, std::ostream &str) { // Note: The below call create the PyCompactUnicodeObject.utf8 representation str << '"' << _PepUnicode_AsString(obj) << '"'; + if (!verbose) + return; str << " (" << PyUnicode_GetLength(obj) << ')'; const auto kind = _PepUnicode_KIND(obj); @@ -324,7 +330,11 @@ static void formatPyObjectHelper(PyObject *obj, std::ostream &str) str << "False"; return; } - str << "refs=" << Py_REFCNT(obj) << ", "; + const auto refs = Py_REFCNT(obj); + if (refs == UINT_MAX) // _Py_IMMORTAL_REFCNT + str << "immortal, "; + else + str << "refs=" << refs << ", "; if (PyType_Check(obj)) { str << "type: "; formatPyTypeObject(reinterpret_cast(obj), str, true); @@ -332,8 +342,15 @@ static void formatPyObjectHelper(PyObject *obj, std::ostream &str) } formatPyTypeObject(obj->ob_type, str, false); str << ", "; - if (PyLong_Check(obj)) - str << PyLong_AsLong(obj); + if (PyLong_Check(obj)) { + const auto llv = PyLong_AsLongLong(obj); + if (PyErr_Occurred() != PyExc_OverflowError) { + str << llv; + } else { + PyErr_Clear(); + str << "0x" << std::hex << PyLong_AsUnsignedLongLong(obj) << std::dec; + } + } else if (PyFloat_Check(obj)) str << PyFloat_AsDouble(obj); else if (PyUnicode_Check(obj)) @@ -419,6 +436,18 @@ std::ostream &operator<<(std::ostream &str, const debugPyBuffer &b) return str; } +std::ios_base &debugVerbose(std::ios_base &s) +{ + verbose = true; + return s; +} + +std::ios_base &debugBrief(std::ios_base &s) +{ + verbose = false; + return s; +} + #ifdef _WIN32 // Converts a Unicode string to a string encoded in the Windows console's // code page via wchar_t for use with argv (PYSIDE-1425). diff --git a/sources/shiboken6/libshiboken/helper.h b/sources/shiboken6/libshiboken/helper.h index 4e14b8c4..f226e8c2 100644 --- a/sources/shiboken6/libshiboken/helper.h +++ b/sources/shiboken6/libshiboken/helper.h @@ -112,7 +112,8 @@ LIBSHIBOKEN_API std::ostream &operator<<(std::ostream &str, const debugSbkObject LIBSHIBOKEN_API std::ostream &operator<<(std::ostream &str, const debugPyTypeObject &o); LIBSHIBOKEN_API std::ostream &operator<<(std::ostream &str, const debugPyBuffer &b); LIBSHIBOKEN_API std::ostream &operator<<(std::ostream &str, const debugPyArrayObject &b); - +LIBSHIBOKEN_API std::ios_base &debugVerbose(std::ios_base &s); +LIBSHIBOKEN_API std::ios_base &debugBrief(std::ios_base &s); } // namespace Shiboken diff --git a/sources/shiboken6/libshiboken/pep384ext.h b/sources/shiboken6/libshiboken/pep384ext.h new file mode 100644 index 00000000..021c53d1 --- /dev/null +++ b/sources/shiboken6/libshiboken/pep384ext.h @@ -0,0 +1,89 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef PEP384EXT_H +#define PEP384EXT_H + +#include "pep384impl.h" + +/// Returns the allocator slot of the PyTypeObject. +inline allocfunc PepExt_Type_GetAllocSlot(PyTypeObject *t) +{ + return reinterpret_cast(PepType_GetSlot(t, Py_tp_alloc)); +} + +/// Invokes the allocator slot of the PyTypeObject. +template +inline Type *PepExt_TypeCallAlloc(PyTypeObject *t, Py_ssize_t nitems) +{ + PyObject *result = PepExt_Type_GetAllocSlot(t)(t, nitems); + return reinterpret_cast(result); +} + +/// Returns the getattro slot of the PyTypeObject. +inline getattrofunc PepExt_Type_GetGetAttroSlot(PyTypeObject *t) +{ + return reinterpret_cast(PepType_GetSlot(t, Py_tp_getattro)); +} + +/// Returns the setattro slot of the PyTypeObject. +inline setattrofunc PepExt_Type_GetSetAttroSlot(PyTypeObject *t) +{ + return reinterpret_cast(PepType_GetSlot(t, Py_tp_setattro)); +} + +/// Returns the descr_get slot of the PyTypeObject. +inline descrgetfunc PepExt_Type_GetDescrGetSlot(PyTypeObject *t) +{ + return reinterpret_cast(PepType_GetSlot(t, Py_tp_descr_get)); +} + +/// Invokes the descr_get slot of the PyTypeObject. +inline PyObject *PepExt_Type_CallDescrGet(PyObject *self, PyObject *obj, PyObject *type) +{ + return PepExt_Type_GetDescrGetSlot(Py_TYPE(self))(self, obj, type); +} + +/// Returns the descr_set slot of the PyTypeObject. +inline descrsetfunc PepExt_Type_GetDescrSetSlot(PyTypeObject *t) +{ + return reinterpret_cast(PepType_GetSlot(t, Py_tp_descr_set)); +} + +/// Returns the call slot of the PyTypeObject. +inline ternaryfunc PepExt_Type_GetCallSlot(PyTypeObject *t) +{ + return reinterpret_cast(PepType_GetSlot(t, Py_tp_call)); +} + +/// Returns the new slot of the PyTypeObject. +inline newfunc PepExt_Type_GetNewSlot(PyTypeObject *t) +{ + return reinterpret_cast(PepType_GetSlot(t, Py_tp_new)); +} + +/// Returns the init slot of the PyTypeObject. +inline initproc PepExt_Type_GetInitSlot(PyTypeObject *t) +{ + return reinterpret_cast(PepType_GetSlot(t, Py_tp_init)); +} + +/// Returns the free slot of the PyTypeObject. +inline freefunc PepExt_Type_GetFreeSlot(PyTypeObject *t) +{ + return reinterpret_cast(PepType_GetSlot(t, Py_tp_free)); +} + +/// Invokes the free slot of the PyTypeObject. +inline void PepExt_TypeCallFree(PyTypeObject *t, void *object) +{ + PepExt_Type_GetFreeSlot(t)(object); +} + +/// Invokes the free slot of the PyTypeObject. +inline void PepExt_TypeCallFree(PyObject *object) +{ + PepExt_Type_GetFreeSlot(Py_TYPE(object))(object); +} + +#endif // PEP384EXT_H diff --git a/sources/shiboken6/libshiboken/pep384impl.cpp b/sources/shiboken6/libshiboken/pep384impl.cpp index 37e51921..2b04af85 100644 --- a/sources/shiboken6/libshiboken/pep384impl.cpp +++ b/sources/shiboken6/libshiboken/pep384impl.cpp @@ -105,13 +105,13 @@ static PyType_Spec typeprobe_spec = { static void check_PyTypeObject_valid() { - auto *obtype = reinterpret_cast(&PyType_Type); - auto *probe_tp_base = reinterpret_cast( - PyObject_GetAttr(obtype, Shiboken::PyMagicName::base())); + auto *typetype = &PyType_Type; + auto *obtype = reinterpret_cast(typetype); + auto *probe_tp_base_obj = PyObject_GetAttr(obtype, Shiboken::PyMagicName::base()); + auto *probe_tp_base = reinterpret_cast(probe_tp_base_obj); auto *probe_tp_bases = PyObject_GetAttr(obtype, Shiboken::PyMagicName::bases()); - auto *check = reinterpret_cast( - PyType_FromSpecWithBases(&typeprobe_spec, probe_tp_bases)); - auto *typetype = reinterpret_cast(obtype); + auto *checkObj = PyType_FromSpecWithBases(&typeprobe_spec, probe_tp_bases); + auto *check = reinterpret_cast(checkObj); PyObject *w = PyObject_GetAttr(obtype, Shiboken::PyMagicName::weakrefoffset()); long probe_tp_weakrefoffset = PyLong_AsLong(w); PyObject *d = PyObject_GetAttr(obtype, Shiboken::PyMagicName::dictoffset()); @@ -149,8 +149,8 @@ check_PyTypeObject_valid() || probe_tp_mro != typetype->tp_mro || Py_TPFLAGS_DEFAULT != (check->tp_flags & Py_TPFLAGS_DEFAULT)) Py_FatalError("The structure of type objects has changed!"); - Py_DECREF(check); - Py_DECREF(probe_tp_base); + Py_DECREF(checkObj); + Py_DECREF(probe_tp_base_obj); Py_DECREF(w); Py_DECREF(d); Py_DECREF(probe_tp_bases); @@ -482,6 +482,22 @@ Pep_GetVerboseFlag() } #endif // Py_LIMITED_API +// Support for pyerrors.h + +#if defined(Py_LIMITED_API) || PY_VERSION_HEX < 0x030C0000 +// Emulate PyErr_GetRaisedException() using the deprecated PyErr_Fetch()/PyErr_Store() +PyObject *PepErr_GetRaisedException() +{ + PyObject *type{}; + PyObject *value{}; + PyObject *traceback{}; + PyErr_Fetch(&type, &value, &traceback); + Py_XINCREF(value); + PyErr_Restore(type, value, traceback); + return value; +} +#endif // Limited or < 3.12 + /***************************************************************************** * * Support for code.h @@ -722,11 +738,8 @@ PyTypeObject *PepStaticMethod_TypePtr = nullptr; static PyTypeObject * getStaticMethodType(void) { - // this works for Python 3, only - // "StaticMethodType = type(str.__dict__['maketrans'])\n"; static const char prog[] = - "from xxsubtype import spamlist\n" - "result = type(spamlist.__dict__['staticmeth'])\n"; + "result = type(str.__dict__['maketrans'])\n"; return reinterpret_cast(PepRun_GetResult(prog)); } @@ -994,33 +1007,108 @@ long _PepRuntimeVersion() * */ +/////////////////////////////////////////////////////////////////////// +// +// PEP 697: Support for embedded type structures. +// +// According to `https://docs.python.org/3/c-api/object.html?highlight=pyobject_gettypedata#c.PyObject_GetTypeData` +// the function `PyObject_GetTypeData` should belong to the Stable API +// since version 3.12.0, but it does not. We use instead some copies +// from Python source code. + +#if !defined(Py_LIMITED_API) && PY_VERSION_HEX >= 0x030C0000 + +# define PepObject_GetTypeData PyObject_GetTypeData + +SbkObjectTypePrivate *PepType_SOTP(PyTypeObject *type) +{ + // PYSIDE-2676: Use the meta type explicitly. + // A derived type would fail the offset calculation. + static auto *meta = SbkObjectType_TypeF(); + assert(SbkObjectType_Check(type)); + auto *obType = reinterpret_cast(type); + void *data = PyObject_GetTypeData(obType, meta); + return reinterpret_cast(data); +} + +void PepType_SOTP_delete(PyTypeObject * /*type*/) +{ +} + +#else + +// The following comments are directly copied from Python 3.12 +// + +// Make sure we have maximum alignment, even if the current compiler +// does not support max_align_t. Note that: +// - Autoconf reports alignment of unknown types to 0. +// - 'long double' has maximum alignment on *most* platforms, +// looks like the best we can do for pre-C11 compilers. +// - The value is tested, see test_alignof_max_align_t +# if !defined(ALIGNOF_MAX_ALIGN_T) || ALIGNOF_MAX_ALIGN_T == 0 +# undef ALIGNOF_MAX_ALIGN_T +# define ALIGNOF_MAX_ALIGN_T alignof(long double) +# endif + +/* Align up to the nearest multiple of alignof(max_align_t) + * (like _Py_ALIGN_UP, but for a size rather than pointer) + */ +static Py_ssize_t _align_up(Py_ssize_t size) +{ + return (size + ALIGNOF_MAX_ALIGN_T - 1) & ~(ALIGNOF_MAX_ALIGN_T - 1); +} + +static void *PepObject_GetTypeData(PyObject *obj, PyTypeObject *cls) +{ + assert(PyObject_TypeCheck(obj, cls)); + return reinterpret_cast(obj) + _align_up(cls->tp_base->tp_basicsize); +} +// +/////////////////////////////////////////////////////////////////////// + /* * PyTypeObject extender */ + static std::unordered_map SOTP_extender{}; static thread_local PyTypeObject *SOTP_key{}; static thread_local SbkObjectTypePrivate *SOTP_value{}; -SbkObjectTypePrivate *PepType_SOTP(PyTypeObject *sbkType) +SbkObjectTypePrivate *PepType_SOTP(PyTypeObject *type) { - if (sbkType == SOTP_key) + static auto *meta = SbkObjectType_TypeF(); + static bool use_312 = _PepRuntimeVersion() >= 0x030C00; + assert(SbkObjectType_Check(type)); + if (use_312) { + auto *obType = reinterpret_cast(type); + void *data = PepObject_GetTypeData(obType, meta); + return reinterpret_cast(data); + } + if (type == SOTP_key) return SOTP_value; - auto it = SOTP_extender.find(sbkType); + auto it = SOTP_extender.find(type); if (it == SOTP_extender.end()) { - it = SOTP_extender.insert({sbkType, {}}).first; + it = SOTP_extender.insert({type, {}}).first; memset(&it->second, 0, sizeof(SbkObjectTypePrivate)); } - SOTP_key = sbkType; + SOTP_key = type; SOTP_value = &it->second; return SOTP_value; } -void PepType_SOTP_delete(PyTypeObject *sbkType) +void PepType_SOTP_delete(PyTypeObject *type) { - SOTP_extender.erase(sbkType); + static bool use_312 = _PepRuntimeVersion() >= 0x030C00; + assert(SbkObjectType_Check(type)); + if (use_312) + return; + SOTP_extender.erase(type); SOTP_key = nullptr; } +#endif // !defined(Py_LIMITED_API) && PY_VERSION_HEX >= 0x030C0000 + /* * SbkEnumType extender */ @@ -1030,6 +1118,7 @@ static thread_local SbkEnumTypePrivate *SETP_value{}; SbkEnumTypePrivate *PepType_SETP(SbkEnumType *enumType) { + // PYSIDE-2230: This makes no sense at all for Enum types. if (enumType == SETP_key) return SETP_value; auto it = SETP_extender.find(enumType); @@ -1087,6 +1176,39 @@ int PepType_SetDict(PyTypeObject *type, PyObject *dict) return 0; } +// Pre 3.10, PyType_GetSlot() would only work for heap types. +// FIXME: PyType_GetSlot() can be used unconditionally when the +// minimum limited API version is >= 3.10. +void *PepType_GetSlot(PyTypeObject *type, int aSlot) +{ + static const bool is310 = _PepRuntimeVersion() >= 0x030A00; + if (is310 || (type->tp_flags & Py_TPFLAGS_HEAPTYPE) != 0) + return PyType_GetSlot(type, aSlot); + + switch (aSlot) { + case Py_tp_alloc: + return reinterpret_cast(type->tp_alloc); + case Py_tp_getattro: + return reinterpret_cast(type->tp_getattro); + case Py_tp_setattro: + return reinterpret_cast(type->tp_setattro); + case Py_tp_descr_get: + return reinterpret_cast(type->tp_descr_get); + case Py_tp_descr_set: + return reinterpret_cast(type->tp_descr_set); + case Py_tp_call: + return reinterpret_cast(type->tp_call); + case Py_tp_new: + return reinterpret_cast(type->tp_new); + case Py_tp_init: + return reinterpret_cast(type->tp_init); + case Py_tp_free: + return reinterpret_cast(type->tp_free); + } + assert(false); + return nullptr; +} + /*************************************************************************** * * PYSIDE-535: The enum/flag error diff --git a/sources/shiboken6/libshiboken/pep384impl.h b/sources/shiboken6/libshiboken/pep384impl.h index 39a6f3d1..e1bf773b 100644 --- a/sources/shiboken6/libshiboken/pep384impl.h +++ b/sources/shiboken6/libshiboken/pep384impl.h @@ -4,11 +4,6 @@ #ifndef PEP384IMPL_H #define PEP384IMPL_H -// PYSIDE-1436: Adapt to Python 3.10 -#if PY_VERSION_HEX < 0x030900A4 -# define Py_SET_REFCNT(obj, refcnt) ((Py_REFCNT(obj) = (refcnt)), (void)0) -#endif - extern "C" { @@ -55,46 +50,79 @@ typedef struct _typeobject { const char *tp_name; Py_ssize_t tp_basicsize; void *X03; // Py_ssize_t tp_itemsize; +#ifdef PEP384_INTERN destructor tp_dealloc; +#else + destructor X04; +#endif void *X05; // Py_ssize_t tp_vectorcall_offset; void *X06; // getattrfunc tp_getattr; void *X07; // setattrfunc tp_setattr; void *X08; // PyAsyncMethods *tp_as_async; +#ifdef PEP384_INTERN reprfunc tp_repr; +#else + reprfunc X09; +#endif void *X10; // PyNumberMethods *tp_as_number; void *X11; // PySequenceMethods *tp_as_sequence; void *X12; // PyMappingMethods *tp_as_mapping; void *X13; // hashfunc tp_hash; +#ifdef PEP384_INTERN ternaryfunc tp_call; - reprfunc tp_str; +#else + ternaryfunc X14; +#endif + reprfunc tp_str; // Only used for PEP384_INTERN and a shiboken test getattrofunc tp_getattro; setattrofunc tp_setattro; void *X18; // PyBufferProcs *tp_as_buffer; unsigned long tp_flags; void *X20; // const char *tp_doc; +#ifdef PEP384_INTERN traverseproc tp_traverse; inquiry tp_clear; +#else + traverseproc X21; + inquiry X22; +#endif void *X23; // richcmpfunc tp_richcompare; Py_ssize_t tp_weaklistoffset; void *X25; // getiterfunc tp_iter; +#ifdef PEP384_INTERN iternextfunc tp_iternext; +#else + iternextfunc X26; +#endif struct PyMethodDef *tp_methods; struct PyMemberDef *tp_members; struct PyGetSetDef *tp_getset; struct _typeobject *tp_base; #ifdef PEP384_INTERN PyObject *tp_dict; + descrgetfunc tp_descr_get; + descrsetfunc tp_descr_set; #else void *X31; + descrgetfunc X32; + descrsetfunc X33; #endif - descrgetfunc tp_descr_get; - descrsetfunc tp_descr_set; Py_ssize_t tp_dictoffset; +#ifdef PEP384_INTERN initproc tp_init; allocfunc tp_alloc; +#else + initproc X39; + allocfunc X40; +#endif newfunc tp_new; +#ifdef PEP384_INTERN freefunc tp_free; inquiry tp_is_gc; /* For PyObject_IS_GC */ +#else + freefunc X41; + inquiry X42; /* For PyObject_IS_GC */ +#endif PyObject *tp_bases; PyObject *tp_mro; /* method resolution order */ @@ -160,6 +188,13 @@ LIBSHIBOKEN_API int Pep_GetFlag(const char *name); LIBSHIBOKEN_API int Pep_GetVerboseFlag(void); #endif +// pyerrors.h +#if defined(Py_LIMITED_API) || PY_VERSION_HEX < 0x030C0000 +LIBSHIBOKEN_API PyObject *PepErr_GetRaisedException(); +#else +# define PepErr_GetRaisedException PyErr_GetRaisedException +#endif + /***************************************************************************** * * RESOLVED: unicodeobject.h @@ -396,6 +431,8 @@ LIBSHIBOKEN_API int PepCode_Check(PyObject *o); # define PepCode_GET_FLAGS(o) PepCode_Get(o, "co_flags") # define PepCode_GET_ARGCOUNT(o) PepCode_Get(o, "co_argcount") +LIBSHIBOKEN_API PyObject *PepFunction_GetDefaults(PyObject *function); + /* Masks for co_flags above */ # define CO_OPTIMIZED 0x0001 # define CO_NEWLOCALS 0x0002 @@ -555,6 +592,8 @@ LIBSHIBOKEN_API PyObject *PepType_GetDict(PyTypeObject *type); // is no longer considered to be accessible, we treat it as such. LIBSHIBOKEN_API int PepType_SetDict(PyTypeObject *type, PyObject *dict); +LIBSHIBOKEN_API void *PepType_GetSlot(PyTypeObject *type, int aSlot); + /***************************************************************************** * * Module Initialization diff --git a/sources/shiboken6/libshiboken/pyobjectholder.h b/sources/shiboken6/libshiboken/pyobjectholder.h new file mode 100644 index 00000000..857748c2 --- /dev/null +++ b/sources/shiboken6/libshiboken/pyobjectholder.h @@ -0,0 +1,86 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef PYOBJECTHOLDER_H +#define PYOBJECTHOLDER_H + +#include "sbkpython.h" + +#include +#include + +namespace Shiboken +{ + +/// PyObjectHolder holds a PyObject pointer, keeping a reference decrementing +/// its reference counter when destroyed. It makes sure to hold the GIL when +/// releasing. It implements copy/move semantics and is mainly intended as a +/// base class for functors holding a callable which can be passed around and +/// stored in containers or moved from freely. +/// For one-shot functors, release() can be invoked after the call. +class PyObjectHolder +{ +public: + PyObjectHolder() noexcept = default; + + /// PyObjectHolder constructor. + /// \param pyobj A reference to a Python object + explicit PyObjectHolder(PyObject *pyObj) noexcept : m_pyObj(pyObj) + { + assert(pyObj != nullptr); + Py_INCREF(m_pyObj); + } + + PyObjectHolder(const PyObjectHolder &o) noexcept : m_pyObj(o.m_pyObj) + { + Py_XINCREF(m_pyObj); + } + + PyObjectHolder &operator=(const PyObjectHolder &o) noexcept + { + if (this != &o) { + m_pyObj = o.m_pyObj; + Py_XINCREF(m_pyObj); + } + return *this; + } + + PyObjectHolder(PyObjectHolder &&o) noexcept : m_pyObj{std::exchange(o.m_pyObj, nullptr)} {} + + PyObjectHolder &operator=(PyObjectHolder &&o) noexcept + { + m_pyObj = std::exchange(o.m_pyObj, nullptr); + return *this; + } + + /// Decref the python reference + ~PyObjectHolder() { release(); } + + [[nodiscard]] bool isNull() const { return m_pyObj == nullptr; } + [[nodiscard]] operator bool() const { return m_pyObj != nullptr; } + + /// Returns the pointer of the Python object being held. + [[nodiscard]] PyObject *object() const { return m_pyObj; } + [[nodiscard]] operator PyObject *() const { return m_pyObj; } + + [[nodiscard]] PyObject *operator->() { return m_pyObj; } + +protected: + void release() + { + if (m_pyObj != nullptr) { + assert(Py_IsInitialized()); + auto gstate = PyGILState_Ensure(); + Py_DECREF(m_pyObj); + PyGILState_Release(gstate); + m_pyObj = nullptr; + } + } + +private: + PyObject *m_pyObj = nullptr; +}; + +} // namespace Shiboken + +#endif // PYOBJECTHOLDER_H diff --git a/sources/shiboken6/libshiboken/sbkcontainer.h b/sources/shiboken6/libshiboken/sbkcontainer.h index 96faf95e..8ad5aadc 100644 --- a/sources/shiboken6/libshiboken/sbkcontainer.h +++ b/sources/shiboken6/libshiboken/sbkcontainer.h @@ -63,7 +63,8 @@ public: static PyObject *tpNew(PyTypeObject *subtype, PyObject * /* args */, PyObject * /* kwds */) { - auto *me = reinterpret_cast(subtype->tp_alloc(subtype, 0)); + allocfunc allocFunc = reinterpret_cast(PepType_GetSlot(subtype, Py_tp_alloc)); + auto *me = reinterpret_cast(allocFunc(subtype, 0)); auto *d = new ShibokenSequenceContainerPrivate; d->m_list = new SequenceContainer; d->m_ownsList = true; @@ -73,10 +74,9 @@ public: static PyObject *tpNewInvalid(PyTypeObject * /* subtype */, PyObject * /* args */, PyObject * /* kwds */) { - PyErr_Format(PyExc_NotImplementedError, + return PyErr_Format(PyExc_NotImplementedError, "Opaque containers of type '%s' cannot be instantiated.", typeid(SequenceContainer).name()); - return nullptr; } static int tpInit(PyObject * /* self */, PyObject * /* args */, PyObject * /* kwds */) @@ -91,7 +91,9 @@ public: if (d->m_ownsList) delete d->m_list; delete d; - Py_TYPE(pySelf)->tp_base->tp_free(self); + auto freeFunc = reinterpret_cast(PepType_GetSlot(Py_TYPE(pySelf)->tp_base, + Py_tp_free)); + freeFunc(self); } static Py_ssize_t sqLen(PyObject *self) @@ -102,10 +104,8 @@ public: static PyObject *sqGetItem(PyObject *self, Py_ssize_t i) { auto *d = get(self); - if (i < 0 || i >= Py_ssize_t(d->m_list->size())) { - PyErr_SetString(PyExc_IndexError, "index out of bounds"); - return nullptr; - } + if (i < 0 || i >= Py_ssize_t(d->m_list->size())) + return PyErr_Format(PyExc_IndexError, "index out of bounds"); auto it = std::cbegin(*d->m_list); std::advance(it, i); return ShibokenContainerValueConverter::convertValueToPython(*it); @@ -130,14 +130,10 @@ public: static PyObject *push_back(PyObject *self, PyObject *pyArg) { auto *d = get(self); - if (!ShibokenContainerValueConverter::checkValue(pyArg)) { - PyErr_SetString(PyExc_TypeError, "wrong type passed to append."); - return nullptr; - } - if (d->m_const) { - PyErr_SetString(PyExc_TypeError, msgModifyConstContainer); - return nullptr; - } + if (!ShibokenContainerValueConverter::checkValue(pyArg)) + return PyErr_Format(PyExc_TypeError, "wrong type passed to append."); + if (d->m_const) + return PyErr_Format(PyExc_TypeError, msgModifyConstContainer); OptionalValue value = ShibokenContainerValueConverter::convertValueToCpp(pyArg); if (!value.has_value()) @@ -149,14 +145,10 @@ public: static PyObject *push_front(PyObject *self, PyObject *pyArg) { auto *d = get(self); - if (!ShibokenContainerValueConverter::checkValue(pyArg)) { - PyErr_SetString(PyExc_TypeError, "wrong type passed to append."); - return nullptr; - } - if (d->m_const) { - PyErr_SetString(PyExc_TypeError, msgModifyConstContainer); - return nullptr; - } + if (!ShibokenContainerValueConverter::checkValue(pyArg)) + return PyErr_Format(PyExc_TypeError, "wrong type passed to append."); + if (d->m_const) + return PyErr_Format(PyExc_TypeError, msgModifyConstContainer); OptionalValue value = ShibokenContainerValueConverter::convertValueToCpp(pyArg); if (!value.has_value()) @@ -168,10 +160,8 @@ public: static PyObject *clear(PyObject *self) { auto *d = get(self); - if (d->m_const) { - PyErr_SetString(PyExc_TypeError, msgModifyConstContainer); - return nullptr; - } + if (d->m_const) + return PyErr_Format(PyExc_TypeError, msgModifyConstContainer); d->m_list->clear(); Py_RETURN_NONE; @@ -180,10 +170,8 @@ public: static PyObject *pop_back(PyObject *self) { auto *d = get(self); - if (d->m_const) { - PyErr_SetString(PyExc_TypeError, msgModifyConstContainer); - return nullptr; - } + if (d->m_const) + return PyErr_Format(PyExc_TypeError, msgModifyConstContainer); d->m_list->pop_back(); Py_RETURN_NONE; @@ -192,10 +180,8 @@ public: static PyObject *pop_front(PyObject *self) { auto *d = get(self); - if (d->m_const) { - PyErr_SetString(PyExc_TypeError, msgModifyConstContainer); - return nullptr; - } + if (d->m_const) + return PyErr_Format(PyExc_TypeError, msgModifyConstContainer); d->m_list->pop_front(); Py_RETURN_NONE; @@ -205,21 +191,16 @@ public: static PyObject *reserve(PyObject *self, PyObject *pyArg) { auto *d = get(self); - if (PyLong_Check(pyArg) == 0) { - PyErr_SetString(PyExc_TypeError, "wrong type passed to reserve()."); - return nullptr; - } - if (d->m_const) { - PyErr_SetString(PyExc_TypeError, msgModifyConstContainer); - return nullptr; - } + if (PyLong_Check(pyArg) == 0) + return PyErr_Format(PyExc_TypeError, "wrong type passed to reserve()."); + if (d->m_const) + return PyErr_Format(PyExc_TypeError, msgModifyConstContainer); if constexpr (ShibokenContainerHasReserve::value) { const Py_ssize_t size = PyLong_AsSsize_t(pyArg); d->m_list->reserve(size); } else { - PyErr_SetString(PyExc_TypeError, "Container does not support reserve()."); - return nullptr; + return PyErr_Format(PyExc_TypeError, "Container does not support reserve()."); } Py_RETURN_NONE; diff --git a/sources/shiboken6/libshiboken/sbkconverter.cpp b/sources/shiboken6/libshiboken/sbkconverter.cpp index 21519051..09a21ed1 100644 --- a/sources/shiboken6/libshiboken/sbkconverter.cpp +++ b/sources/shiboken6/libshiboken/sbkconverter.cpp @@ -4,6 +4,7 @@ #include "sbkconverter.h" #include "sbkconverter_p.h" #include "sbkarrayconverter_p.h" +#include "sbkmodule.h" #include "basewrapper_p.h" #include "bindingmanager.h" #include "autodecref.h" @@ -12,6 +13,7 @@ #include #include +#include static SbkConverter **PrimitiveTypeConverters; @@ -146,6 +148,13 @@ void addPythonToCppValueConversion(PyTypeObject *type, addPythonToCppValueConversion(sotp->converter, pythonToCppFunc, isConvertibleToCppFunc); } +void addPythonToCppValueConversion(Shiboken::Module::TypeInitStruct typeStruct, + PythonToCppFunc pythonToCppFunc, + IsConvertibleToCppFunc isConvertibleToCppFunc) +{ + addPythonToCppValueConversion(typeStruct.type, pythonToCppFunc, isConvertibleToCppFunc); +} + PyObject *pointerToPython(PyTypeObject *type, const void *cppIn) { auto *sotp = PepType_SOTP(type); @@ -227,6 +236,11 @@ PythonToCppConversion pythonToCppPointerConversion(PyTypeObject *type, PyObject return {}; } +PythonToCppConversion pythonToCppPointerConversion(Module::TypeInitStruct typeStruct, PyObject *pyIn) +{ + return pythonToCppPointerConversion(typeStruct.type, pyIn); +} + static inline PythonToCppFunc IsPythonToCppConvertible(const SbkConverter *converter, PyObject *pyIn) { assert(pyIn); @@ -409,11 +423,47 @@ void registerConverterName(SbkConverter *converter, const char *typeName) converters.insert(std::make_pair(typeName, converter)); } -SbkConverter *getConverter(const char *typeName) +static std::string getRealTypeName(const std::string &typeName) { - ConvertersMap::const_iterator it = converters.find(typeName); + auto size = typeName.size(); + if (std::isalnum(typeName[size - 1]) == 0) + return typeName.substr(0, size - 1); + return typeName; +} + +// PYSIDE-2404: Build a negative cache of already failed lookups. +// The resulting list must be reset after each new import, +// because that can change results. Also clear the cache after +// reaching some threashold. +static std::unordered_set nonExistingTypeNames{}; + +// Arbitrary size limit to prevent random name overflows. +static constexpr std::size_t negativeCacheLimit = 50; + +static void rememberAsNonexistent(const std::string &typeName) +{ + if (nonExistingTypeNames.size() > negativeCacheLimit) + clearNegativeLazyCache(); + converters.insert(std::make_pair(typeName, nullptr)); + nonExistingTypeNames.insert(typeName); +} + +SbkConverter *getConverter(const char *typeNameC) +{ + std::string typeName = typeNameC; + auto it = converters.find(typeName); + // PYSIDE-2404: This can also contain explicit nullptr as a negative cache. + if (it != converters.end()) + return it->second; + // PYSIDE-2404: Did not find the name. Load the lazy classes + // which have this name and try again. + Shiboken::Module::loadLazyClassesWithName(getRealTypeName(typeName).c_str()); + it = converters.find(typeName); if (it != converters.end()) return it->second; + // Cache the negative result. Don't forget to clear the cache for new modules. + rememberAsNonexistent(typeName); + if (Shiboken::pyVerbose() > 0) { const std::string message = std::string("Can't find type resolver for type '") + typeName + "'."; @@ -422,6 +472,15 @@ SbkConverter *getConverter(const char *typeName) return nullptr; } +void clearNegativeLazyCache() +{ + for (const auto &typeName : nonExistingTypeNames) { + auto it = converters.find(typeName); + converters.erase(it); + } + nonExistingTypeNames.clear(); +} + SbkConverter *primitiveTypeConverter(int index) { return PrimitiveTypeConverters[index]; diff --git a/sources/shiboken6/libshiboken/sbkconverter.h b/sources/shiboken6/libshiboken/sbkconverter.h index 539e3286..0d68f3fa 100644 --- a/sources/shiboken6/libshiboken/sbkconverter.h +++ b/sources/shiboken6/libshiboken/sbkconverter.h @@ -5,6 +5,7 @@ #define SBK_CONVERTER_H #include "sbkpython.h" +#include "sbkmodule.h" #include "shibokenmacros.h" #include "sbkenum.h" #include "basewrapper_p.h" @@ -146,6 +147,9 @@ LIBSHIBOKEN_API void addPythonToCppValueConversion(SbkConverter *converter, LIBSHIBOKEN_API void addPythonToCppValueConversion(PyTypeObject *type, PythonToCppFunc pythonToCppFunc, IsConvertibleToCppFunc isConvertibleToCppFunc); +LIBSHIBOKEN_API void addPythonToCppValueConversion(Shiboken::Module::TypeInitStruct typeStruct, + PythonToCppFunc pythonToCppFunc, + IsConvertibleToCppFunc isConvertibleToCppFunc); // C++ -> Python --------------------------------------------------------------------------- @@ -203,6 +207,7 @@ struct PythonToCppConversion */ LIBSHIBOKEN_API PythonToCppFunc isPythonToCppPointerConvertible(PyTypeObject *type, PyObject *pyIn); LIBSHIBOKEN_API PythonToCppConversion pythonToCppPointerConversion(PyTypeObject *type, PyObject *pyIn); +LIBSHIBOKEN_API PythonToCppConversion pythonToCppPointerConversion(Module::TypeInitStruct typeStruct, PyObject *pyIn); /** * Returns a Python to C++ conversion function if the Python object is convertible to a C++ value. diff --git a/sources/shiboken6/libshiboken/sbkconverter_p.h b/sources/shiboken6/libshiboken/sbkconverter_p.h index fbb12480..9b2d5dba 100644 --- a/sources/shiboken6/libshiboken/sbkconverter_p.h +++ b/sources/shiboken6/libshiboken/sbkconverter_p.h @@ -327,7 +327,7 @@ struct FloatPrimitive : TwoPrimitive } static void toCpp(PyObject *pyIn, void *cppOut) { - *reinterpret_cast(cppOut) = FLOAT(PyLong_AsLongLong(pyIn)); + *reinterpret_cast(cppOut) = FLOAT(PyLong_AsDouble(pyIn)); } static PythonToCppFunc isConvertible(PyObject *pyIn) { @@ -531,6 +531,10 @@ SbkConverter *createConverterObject(PyTypeObject *type, IsConvertibleToCppFunc toCppPointerCheckFunc, CppToPythonFunc pointerToPythonFunc, CppToPythonFunc copyToPythonFunc); + +/// Interface for sbkmodule which must reset cache when new module is loaded. +LIBSHIBOKEN_API void clearNegativeLazyCache(); + } // namespace Shiboken::Conversions #endif // SBK_CONVERTER_P_H diff --git a/sources/shiboken6/libshiboken/sbkcppstring.cpp b/sources/shiboken6/libshiboken/sbkcppstring.cpp index 42b09111..8e8324f5 100644 --- a/sources/shiboken6/libshiboken/sbkcppstring.cpp +++ b/sources/shiboken6/libshiboken/sbkcppstring.cpp @@ -12,6 +12,11 @@ PyObject *fromCppString(const std::string &value) return PyUnicode_FromStringAndSize(value.data(), value.size()); } +PyObject *fromCppStringView(std::string_view value) +{ + return PyUnicode_FromStringAndSize(value.data(), value.size()); +} + PyObject *fromCppWString(const std::wstring &value) { return PyUnicode_FromWideChar(value.data(), value.size()); diff --git a/sources/shiboken6/libshiboken/sbkcppstring.h b/sources/shiboken6/libshiboken/sbkcppstring.h index f418ea8d..7ffe11c7 100644 --- a/sources/shiboken6/libshiboken/sbkcppstring.h +++ b/sources/shiboken6/libshiboken/sbkcppstring.h @@ -8,10 +8,12 @@ #include "shibokenmacros.h" #include +#include namespace Shiboken::String { LIBSHIBOKEN_API PyObject *fromCppString(const std::string &value); + LIBSHIBOKEN_API PyObject *fromCppStringView(std::string_view value); LIBSHIBOKEN_API PyObject *fromCppWString(const std::wstring &value); LIBSHIBOKEN_API void toCppString(PyObject *str, std::string *value); LIBSHIBOKEN_API void toCppWString(PyObject *str, std::wstring *value); diff --git a/sources/shiboken6/libshiboken/sbkcpptonumpy.cpp b/sources/shiboken6/libshiboken/sbkcpptonumpy.cpp index 44e900f0..7637efa7 100644 --- a/sources/shiboken6/libshiboken/sbkcpptonumpy.cpp +++ b/sources/shiboken6/libshiboken/sbkcpptonumpy.cpp @@ -49,17 +49,17 @@ PyObject *createByteArray1(Py_ssize_t, const uint8_t *) PyObject *createDoubleArray1(Py_ssize_t, const double *) { - return Py_None; + Py_RETURN_NONE; } PyObject *createFloatArray1(Py_ssize_t, const float *) { - return Py_None; + Py_RETURN_NONE; } PyObject *createIntArray1(Py_ssize_t, const int *) { - return Py_None; + Py_RETURN_NONE; } #endif // !HAVE_NUMPY diff --git a/sources/shiboken6/libshiboken/sbkenum.cpp b/sources/shiboken6/libshiboken/sbkenum.cpp index d3936997..4c0597bd 100644 --- a/sources/shiboken6/libshiboken/sbkenum.cpp +++ b/sources/shiboken6/libshiboken/sbkenum.cpp @@ -3,6 +3,7 @@ #include "sbkenum.h" #include "sbkstring.h" +#include "helper.h" #include "sbkstaticstrings.h" #include "sbkstaticstrings_p.h" #include "sbkconverter.h" @@ -306,6 +307,8 @@ static PyTypeObject *createEnumForPython(PyObject *scopeOrModule, enumName = PyDict_GetItem(sotp->enumTypeDict, name); } + SBK_UNUSED(getPyEnumMeta()); // enforce PyEnumModule creation + assert(PyEnumModule != nullptr); AutoDecRef PyEnumType(PyObject_GetAttr(PyEnumModule, enumName)); assert(PyEnumType.object()); bool isFlag = PyObject_IsSubclass(PyEnumType, PyFlag); diff --git a/sources/shiboken6/libshiboken/sbkfeature_base.cpp b/sources/shiboken6/libshiboken/sbkfeature_base.cpp index 906df8e1..f31b8f4f 100644 --- a/sources/shiboken6/libshiboken/sbkfeature_base.cpp +++ b/sources/shiboken6/libshiboken/sbkfeature_base.cpp @@ -4,6 +4,7 @@ #include "basewrapper.h" #include "basewrapper_p.h" #include "autodecref.h" +#include "pep384ext.h" #include "sbkenum.h" #include "sbkstring.h" #include "sbkstaticstrings.h" @@ -37,11 +38,19 @@ int currentSelectId(PyTypeObject *type) } static SelectableFeatureHook SelectFeatureSet = nullptr; +static SelectableFeatureCallback featureCb = nullptr; + +void setSelectableFeatureCallback(SelectableFeatureCallback func) +{ + featureCb = func; +} SelectableFeatureHook initSelectableFeature(SelectableFeatureHook func) { auto ret = SelectFeatureSet; SelectFeatureSet = func; + if (featureCb) + featureCb(SelectFeatureSet != nullptr); return ret; } // @@ -56,14 +65,24 @@ void disassembleFrame(const char *marker) static PyObject *dismodule = PyImport_ImportModule("dis"); static PyObject *disco = PyObject_GetAttrString(dismodule, "disco"); static PyObject *const _f_lasti = Shiboken::String::createStaticString("f_lasti"); + static PyObject *const _f_lineno = Shiboken::String::createStaticString("f_lineno"); static PyObject *const _f_code = Shiboken::String::createStaticString("f_code"); - auto *frame = reinterpret_cast(PyEval_GetFrame()); - AutoDecRef f_lasti(PyObject_GetAttr(frame, _f_lasti)); - AutoDecRef f_code(PyObject_GetAttr(frame, _f_code)); + static PyObject *const _co_filename = Shiboken::String::createStaticString("co_filename"); AutoDecRef ignore{}; - fprintf(stdout, "\n%s BEGIN\n", marker); - ignore.reset(PyObject_CallFunctionObjArgs(disco, f_code.object(), f_lasti.object(), nullptr)); - fprintf(stdout, "%s END\n\n", marker); + auto *frame = reinterpret_cast(PyEval_GetFrame()); + if (frame == nullptr) { + fprintf(stdout, "\n%s BEGIN no frame END\n\n", marker); + } else { + AutoDecRef f_lasti(PyObject_GetAttr(frame, _f_lasti)); + AutoDecRef f_lineno(PyObject_GetAttr(frame, _f_lineno)); + AutoDecRef f_code(PyObject_GetAttr(frame, _f_code)); + AutoDecRef co_filename(PyObject_GetAttr(f_code, _co_filename)); + long line = PyLong_AsLong(f_lineno); + const char *fname = String::toCString(co_filename); + fprintf(stdout, "\n%s BEGIN line=%ld %s\n", marker, line, fname); + ignore.reset(PyObject_CallFunctionObjArgs(disco, f_code.object(), f_lasti.object(), nullptr)); + fprintf(stdout, "%s END line=%ld %s\n\n", marker, line, fname); + } #if PY_VERSION_HEX >= 0x030C0000 && !Py_LIMITED_API if (error_type) PyErr_DisplayException(error_value); @@ -101,7 +120,7 @@ static bool currentOpcode_Is_CallMethNoArgs() // We look into the currently active operation if we are going to call // a method with zero arguments. auto *frame = PyEval_GetFrame(); -#if PY_VERSION_HEX >= 0x03090000 && !Py_LIMITED_API && !defined(PYPY_VERSION) +#if !Py_LIMITED_API && !defined(PYPY_VERSION) auto *f_code = PyFrame_GetCode(frame); #else static PyObject *const _f_code = Shiboken::String::createStaticString("f_code"); @@ -300,7 +319,7 @@ PyObject *mangled_type_getattro(PyTypeObject *type, PyObject *name) * with the complex `tp_getattro` of `QObject` and other instances. * What we change here is the meta class of `QObject`. */ - static getattrofunc const type_getattro = PyType_Type.tp_getattro; + static getattrofunc const type_getattro = PepExt_Type_GetGetAttroSlot(&PyType_Type); static PyObject *const ignAttr1 = PyName::qtStaticMetaObject(); static PyObject *const ignAttr2 = PyMagicName::get(); static PyTypeObject *const EnumMeta = getPyEnumMeta(); diff --git a/sources/shiboken6/libshiboken/sbkmodule.cpp b/sources/shiboken6/libshiboken/sbkmodule.cpp index aeae34a3..adcd6756 100644 --- a/sources/shiboken6/libshiboken/sbkmodule.cpp +++ b/sources/shiboken6/libshiboken/sbkmodule.cpp @@ -2,54 +2,485 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "sbkmodule.h" +#include "autodecref.h" #include "basewrapper.h" #include "bindingmanager.h" +#include "sbkstring.h" +#include "sbkcppstring.h" +#include "sbkconverter_p.h" + #include +#include +#include /// This hash maps module objects to arrays of Python types. -using ModuleTypesMap = std::unordered_map ; +using ModuleTypesMap = std::unordered_map ; /// This hash maps module objects to arrays of converters. using ModuleConvertersMap = std::unordered_map; +/// This hash maps type names to type creation functions. +using TypeCreationFunctionModulePair = + std::pair; +using NameToTypeFunctionMap = std::unordered_map; + +/// This hash maps module objects to maps of names to functions. +using ModuleToFuncsMap = std::unordered_map ; + /// All types produced in imported modules are mapped here. static ModuleTypesMap moduleTypes; static ModuleConvertersMap moduleConverters; +static ModuleToFuncsMap moduleToFuncs; namespace Shiboken { namespace Module { +// PYSIDE-2404: Replacing the arguments generated by cpythonTypeNameExt +// by a function call. +LIBSHIBOKEN_API PyTypeObject *get(TypeInitStruct &typeStruct) +{ + if (typeStruct.type != nullptr) + return typeStruct.type; + + static PyObject *sysModules = PyImport_GetModuleDict(); + + // The slow path for initialization. + // We get the type by following the chain from the module. + // As soon as types[index] gets filled, we can stop. + + std::string_view names(typeStruct.fullName); + const bool usePySide = names.compare(0, 8, "PySide6.") == 0; + auto dotPos = usePySide ? names.find('.', 8) : names.find('.'); + auto startPos = dotPos + 1; + AutoDecRef modName(String::fromCppStringView(names.substr(0, dotPos))); + auto *modOrType = PyDict_GetItem(sysModules, modName); + if (modOrType == nullptr) { + PyErr_Format(PyExc_SystemError, "Module \"%U\" should already be in sys.modules", + modName.object()); + return nullptr; + } + + do { + dotPos = names.find('.', startPos); + auto typeName = dotPos != std::string::npos + ? names.substr(startPos, dotPos - startPos) + : names.substr(startPos); + startPos = dotPos + 1; + AutoDecRef obTypeName(String::fromCppStringView(typeName)); + modOrType = PyObject_GetAttr(modOrType, obTypeName); + } while (typeStruct.type == nullptr && dotPos != std::string::npos); + + return typeStruct.type; +} + +static PyTypeObject *incarnateType(PyObject *module, const char *name, + NameToTypeFunctionMap &nameToFunc) +{ + // - locate the name and retrieve the generating function + auto funcIter = nameToFunc.find(name); + if (funcIter == nameToFunc.end()) { + // attribute does really not exist. + PyErr_SetNone(PyExc_AttributeError); + return nullptr; + } + // - call this function that returns a PyTypeObject + auto pair = funcIter->second; + auto initFunc = pair.first; + auto *modOrType = pair.second; + + // PYSIDE-2404: Make sure that no switching happens during type creation. + auto saveFeature = initSelectableFeature(nullptr); + PyTypeObject *type = initFunc(modOrType); + initSelectableFeature(saveFeature); + + // - assign this object to the name in the module + auto *res = reinterpret_cast(type); + Py_INCREF(res); + PyModule_AddObject(module, name, res); // steals reference + // - remove the entry, if not by something cleared. + if (!nameToFunc.empty()) + nameToFunc.erase(funcIter); + // - return the PyTypeObject. + return type; +} + +// PYSIDE-2404: Make sure that the mentioned classes really exist. +// Used in `Pyside::typeName`. Because the result will be cached by +// the creation of the type(s), this is efficient. +void loadLazyClassesWithName(const char *name) +{ + for (auto const & tableIter : moduleToFuncs) { + auto nameToFunc = tableIter.second; + auto funcIter = nameToFunc.find(name); + if (funcIter != nameToFunc.end()) { + // attribute exists in the lazy types. + auto *module = tableIter.first; + incarnateType(module, name, nameToFunc); + } + } +} + +// PYSIDE-2404: Completely load all not yet loaded classes. +// This is needed to resolve a star import. +void resolveLazyClasses(PyObject *module) +{ + // - locate the module in the moduleTofuncs mapping + auto tableIter = moduleToFuncs.find(module); + if (tableIter == moduleToFuncs.end()) + return; + + // - see if there are still unloaded elements + auto &nameToFunc = tableIter->second; + + // - incarnate all types. + while (!nameToFunc.empty()) { + auto it = nameToFunc.begin(); + auto attrNameStr = it->first; + incarnateType(module, attrNameStr.c_str(), nameToFunc); + } +} + +// PYSIDE-2404: Override the gettattr function of modules. +static getattrofunc origModuleGetattro{}; + +// PYSIDE-2404: Use the patched module getattr to do on-demand initialization. +// This modifies _all_ modules but should have no impact. +static PyObject *PyModule_lazyGetAttro(PyObject *module, PyObject *name) +{ + // - check if the attribute is present and return it. + auto *attr = PyObject_GenericGetAttr(module, name); + // - we handle AttributeError, only. + if (!(attr == nullptr && PyErr_ExceptionMatches(PyExc_AttributeError))) + return attr; + + PyErr_Clear(); + // - locate the module in the moduleTofuncs mapping + auto tableIter = moduleToFuncs.find(module); + // - if this is not our module, use the original + if (tableIter == moduleToFuncs.end()) + return origModuleGetattro(module, name); + + // - locate the name and retrieve the generating function + const char *attrNameStr = Shiboken::String::toCString(name); + auto &nameToFunc = tableIter->second; + // - create the real type (incarnateType checks this) + auto *type = incarnateType(module, attrNameStr, nameToFunc); + auto *ret = reinterpret_cast(type); + // - if attribute does really not exist use the original + if (ret == nullptr && PyErr_ExceptionMatches(PyExc_AttributeError)) { + PyErr_Clear(); + return origModuleGetattro(module, name); + } + + return ret; +} + +// PYSIDE-2404: Supply a new module dir for not yet visible entries. +// This modification is only for "our" modules. +static PyObject *_module_dir_template(PyObject * /* self */, PyObject *args) +{ + static PyObject *const _dict = Shiboken::String::createStaticString("__dict__"); + // The dir function must replace all of the builtin function. + PyObject *module{}; + if (!PyArg_ParseTuple(args, "O", &module)) + return nullptr; + + auto tableIter = moduleToFuncs.find(module); + assert(tableIter != moduleToFuncs.end()); + Shiboken::AutoDecRef dict(PyObject_GetAttr(module, _dict)); + auto *ret = PyDict_Keys(dict); + // Now add all elements that were not yet in the dict. + auto &nameToFunc = tableIter->second; + for (const auto &funcIter : nameToFunc) { + const char *name = funcIter.first.c_str(); + Shiboken::AutoDecRef pyName(PyUnicode_FromString(name)); + PyList_Append(ret, pyName); + } + return ret; +} + +static PyMethodDef module_methods[] = { + {"__dir__", (PyCFunction)_module_dir_template, METH_VARARGS, nullptr}, + {nullptr, nullptr, 0, nullptr} +}; + +// Python 3.8 - 3.12 +static int const LOAD_CONST_312 = 100; +static int const IMPORT_NAME_312 = 108; + +static bool isImportStar(PyObject *module) +{ + // Find out whether we have a star import. This must work even + // when we have no import support from feature. + static PyObject *const _f_code = Shiboken::String::createStaticString("f_code"); + static PyObject *const _f_lasti = Shiboken::String::createStaticString("f_lasti"); + static PyObject *const _f_back = Shiboken::String::createStaticString("f_back"); + static PyObject *const _co_code = Shiboken::String::createStaticString("co_code"); + static PyObject *const _co_consts = Shiboken::String::createStaticString("co_consts"); + static PyObject *const _co_names = Shiboken::String::createStaticString("co_names"); + + auto *obFrame = reinterpret_cast(PyEval_GetFrame()); + if (obFrame == nullptr) + return true; // better assume worst-case. + + Py_INCREF(obFrame); + AutoDecRef dec_frame(obFrame); + + // Calculate the offset of the running import_name opcode on the stack. + // Right before that there must be a load_const with the tuple `("*",)`. + while (dec_frame.object() != Py_None) { + AutoDecRef dec_f_code(PyObject_GetAttr(dec_frame, _f_code)); + AutoDecRef dec_co_code(PyObject_GetAttr(dec_f_code, _co_code)); + AutoDecRef dec_f_lasti(PyObject_GetAttr(dec_frame, _f_lasti)); + Py_ssize_t f_lasti = PyLong_AsSsize_t(dec_f_lasti); + Py_ssize_t code_len; + char *co_code{}; + PyBytes_AsStringAndSize(dec_co_code, &co_code, &code_len); + uint8_t opcode2 = co_code[f_lasti]; + uint8_t opcode1 = co_code[f_lasti - 2]; + if (opcode1 == LOAD_CONST_312 && opcode2 == IMPORT_NAME_312) { + uint8_t oparg1 = co_code[f_lasti - 1]; + uint8_t oparg2 = co_code[f_lasti + 1]; + AutoDecRef dec_co_consts(PyObject_GetAttr(dec_f_code, _co_consts)); + auto *fromlist = PyTuple_GetItem(dec_co_consts, oparg1); + if (PyTuple_Check(fromlist) && PyTuple_Size(fromlist) == 1 + && Shiboken::String::toCString(PyTuple_GetItem(fromlist, 0))[0] == '*') { + AutoDecRef dec_co_names(PyObject_GetAttr(dec_f_code, _co_names)); + const char *name = String::toCString(PyTuple_GetItem(dec_co_names, oparg2)); + const char *modName = PyModule_GetName(module); + if (std::strcmp(name, modName) == 0) + return true; + } + } + dec_frame.reset(PyObject_GetAttr(dec_frame, _f_back)); + } + return false; +} + +// PYSIDE-2404: These modules produce ambiguous names which we cannot handle, yet. +static std::unordered_set dontLazyLoad{ + "sample", + "smart", + "testbinding" +}; + +static const std::unordered_set knownModules{ + "shiboken6.Shiboken", + "minimal", + "other", + "sample", + "smart", + "scriptableapplication", + "testbinding" +}; + +static bool canNotLazyLoad(PyObject *module) +{ + const char *modName = PyModule_GetName(module); + + // There are no more things that must be disabled :-D + return dontLazyLoad.find(modName) != dontLazyLoad.end(); +} + +static bool shouldLazyLoad(PyObject *module) +{ + const char *modName = PyModule_GetName(module); + + if (knownModules.find(modName) != knownModules.end()) + return true; + return std::strncmp(modName, "PySide6.", 8) == 0; +} + +static int lazyLoadDefault() +{ +#ifndef PYPY_VERSION + int result = 1; +#else + int result = 0; +#endif + if (auto *flag = getenv("PYSIDE6_OPTION_LAZY")) + result = std::atoi(flag); + return result; +} + +void AddTypeCreationFunction(PyObject *module, + const char *name, + TypeCreationFunction func) +{ + static const int value = lazyLoadDefault(); + + // - locate the module in the moduleTofuncs mapping + auto tableIter = moduleToFuncs.find(module); + assert(tableIter != moduleToFuncs.end()); + // - Assign the name/generating function pair. + auto &nameToFunc = tableIter->second; + TypeCreationFunctionModulePair pair{func, module}; + auto nit = nameToFunc.find(name); + if (nit == nameToFunc.end()) + nameToFunc.insert(std::make_pair(name, pair)); + else + nit->second = pair; + + // PYSIDE-2404: Lazy Loading + // + // Options: + // 0 - switch lazy loading off. + // 1 - lazy loading for all known modules. + // 3 - lazy loading for any module. + // + // By default we lazy load all known modules (option = 1). + + if (value == 0 // completely disabled + || canNotLazyLoad(module) // for some reason we cannot lazy load + || (value == 1 && !shouldLazyLoad(module)) // not a known module + ) { + PyTypeObject *type = func(module); + PyModule_AddObject(module, name, reinterpret_cast(type)); // steals reference + } +} + +void AddTypeCreationFunction(PyObject *module, + const char *name, + TypeCreationFunction func, + const char *containerName) +{ + // This version could be delayed as well, but for the few cases + // we simply fetch the container type and insert directly. + AutoDecRef obContainerType(PyObject_GetAttrString(module, containerName)); + PyTypeObject *type = func(obContainerType); + PyObject_SetAttrString(obContainerType, name, reinterpret_cast(type)); // steals reference +} + +void AddTypeCreationFunction(PyObject *module, + const char *name, + TypeCreationFunction func, + const char *outerContainerName, + const char *innerContainerName) +{ + // This version has even more indirection. It is very rare, and + // we handle it directly. + AutoDecRef obOuterType(PyObject_GetAttrString(module, outerContainerName)); + AutoDecRef obInnerType(PyObject_GetAttrString(obOuterType, innerContainerName)); + PyTypeObject *type = func(obInnerType); + PyObject_SetAttrString(obInnerType, name, reinterpret_cast(type)); // steals reference +} + +void AddTypeCreationFunction(PyObject *module, + const char *name, + TypeCreationFunction func, + const char *containerName3, + const char *containerName2, + const char *containerName) +{ + // This version has even mode indirection. It is very rare, and + // we handle it directly. + AutoDecRef obContainerType3(PyObject_GetAttrString(module, containerName3)); + AutoDecRef obContainerType2(PyObject_GetAttrString(obContainerType3, containerName2)); + AutoDecRef obContainerType(PyObject_GetAttrString(obContainerType2, containerName)); + PyTypeObject *type = func(obContainerType); + PyObject_SetAttrString(obContainerType, name, reinterpret_cast(type)); // steals reference +} + PyObject *import(const char *moduleName) { PyObject *sysModules = PyImport_GetModuleDict(); PyObject *module = PyDict_GetItemString(sysModules, moduleName); - if (module) + if (module != nullptr) Py_INCREF(module); else module = PyImport_ImportModule(moduleName); - if (!module) - PyErr_Format(PyExc_ImportError,"could not import module '%s'", moduleName); + if (module == nullptr) + PyErr_Format(PyExc_ImportError, "could not import module '%s'", moduleName); return module; } -PyObject *create(const char * /* moduleName */, void *moduleData) +// PYSIDE-2404: Redirecting import for "import *" support. +// +// The first import will be handled by the isImportStar function. +// But the same module might be imported twice, which would give no +// introspection due to module caching. + +static PyObject *origImportFunc{}; + +static PyObject *lazy_import(PyObject * /* self */, PyObject *args, PyObject *kwds) +{ + auto *ret = PyObject_Call(origImportFunc, args, kwds); + if (ret != nullptr) { + // PYSIDE-2404: Support star import when lazy loading. + if (PyTuple_Size(args) >= 4) { + auto *fromlist = PyTuple_GetItem(args, 3); + if (PyTuple_Check(fromlist) && PyTuple_Size(fromlist) == 1 + && Shiboken::String::toCString(PyTuple_GetItem(fromlist, 0))[0] == '*') + Shiboken::Module::resolveLazyClasses(ret); + } + } + return ret; +} + +static PyMethodDef lazy_methods[] = { + {"__lazy_import__", (PyCFunction)lazy_import, METH_VARARGS | METH_KEYWORDS, nullptr}, + {nullptr, nullptr, 0, nullptr} +}; + +PyObject *create(const char * /* modName */, void *moduleData) { + static auto *sysModules = PyImport_GetModuleDict(); + static auto *builtins = PyEval_GetBuiltins(); + static auto *partial = Pep_GetPartialFunction(); + static bool lazy_init{}; + Shiboken::init(); - return PyModule_Create(reinterpret_cast(moduleData)); + auto *module = PyModule_Create(reinterpret_cast(moduleData)); + + // Setup of a dir function for "missing" classes. + auto *moduleDirTemplate = PyCFunction_NewEx(module_methods, nullptr, nullptr); + // Turn this function into a bound object, so we have access to the module. + auto *moduleDir = PyObject_CallFunctionObjArgs(partial, moduleDirTemplate, module, nullptr); + PyModule_AddObject(module, module_methods->ml_name, moduleDir); // steals reference + // Insert an initial empty table for the module. + NameToTypeFunctionMap empty; + moduleToFuncs.insert(std::make_pair(module, empty)); + + // A star import must be done unconditionally. Use the complete name. + if (isImportStar(module)) + dontLazyLoad.insert(PyModule_GetName(module)); + + if (!lazy_init) { + // Install the getattr patch. + origModuleGetattro = PyModule_Type.tp_getattro; + PyModule_Type.tp_getattro = PyModule_lazyGetAttro; + // Add the lazy import redirection, keeping a reference. + origImportFunc = PyDict_GetItemString(builtins, "__import__"); + Py_INCREF(origImportFunc); + AutoDecRef func(PyCFunction_NewEx(lazy_methods, nullptr, nullptr)); + PyDict_SetItemString(builtins, "__import__", func); + // Everything is set. + lazy_init = true; + } + // PYSIDE-2404: Nuitka inserts some additional code in standalone mode + // in an invisible virtual module (i.e. `QtCore-postLoad`) + // that gets imported before the running import can call + // `_PyImport_FixupExtensionObject` which does the insertion + // into `sys.modules`. This can cause a race condition. + // Insert the module early into the module dict to prevend recursion. + PyDict_SetItemString(sysModules, PyModule_GetName(module), module); + // Clear the non-existing name cache because we have a new module. + Shiboken::Conversions::clearNegativeLazyCache(); + return module; } -void registerTypes(PyObject *module, PyTypeObject **types) +void registerTypes(PyObject *module, TypeInitStruct *types) { auto iter = moduleTypes.find(module); if (iter == moduleTypes.end()) moduleTypes.insert(std::make_pair(module, types)); } -PyTypeObject **getTypes(PyObject *module) +TypeInitStruct *getTypes(PyObject *module) { auto iter = moduleTypes.find(module); return (iter == moduleTypes.end()) ? 0 : iter->second; diff --git a/sources/shiboken6/libshiboken/sbkmodule.h b/sources/shiboken6/libshiboken/sbkmodule.h index c39692f1..1b3de33b 100644 --- a/sources/shiboken6/libshiboken/sbkmodule.h +++ b/sources/shiboken6/libshiboken/sbkmodule.h @@ -14,6 +14,21 @@ struct SbkConverter; namespace Shiboken::Module { +struct TypeInitStruct +{ + PyTypeObject *type; + const char *fullName; +}; + +/// PYSIDE-2404: Replacing the arguments in cpythonTypeNameExt by a function. +LIBSHIBOKEN_API PyTypeObject *get(TypeInitStruct &typeStruct); + +/// PYSIDE-2404: Make sure that mentioned classes really exist. +LIBSHIBOKEN_API void loadLazyClassesWithName(const char *name); + +/// PYSIDE-2404: incarnate all classes for star imports. +LIBSHIBOKEN_API void resolveLazyClasses(PyObject *module); + /** * Imports and returns the module named \p moduleName, or a NULL pointer in case of failure. * If the module is already imported, it increments its reference count before returning it. @@ -29,19 +44,43 @@ LIBSHIBOKEN_API PyObject *import(const char *moduleName); */ LIBSHIBOKEN_API PyObject *create(const char *moduleName, void *moduleData); +using TypeCreationFunction = PyTypeObject *(*)(PyObject *module); + +/// Adds a type creation function to the module. +LIBSHIBOKEN_API void AddTypeCreationFunction(PyObject *module, + const char *name, + TypeCreationFunction func); + +LIBSHIBOKEN_API void AddTypeCreationFunction(PyObject *module, + const char *name, + TypeCreationFunction func, + const char *containerName); + +LIBSHIBOKEN_API void AddTypeCreationFunction(PyObject *module, + const char *name, + TypeCreationFunction func, + const char *outerContainerName, + const char *innerContainerName); + +LIBSHIBOKEN_API void AddTypeCreationFunction(PyObject *module, + const char *name, + TypeCreationFunction func, + const char *containerName3, + const char *containerName2, + const char *containerName); /** * Registers the list of types created by \p module. * \param module Module where the types were created. * \param types Array of PyTypeObject *objects representing the types created on \p module. */ -LIBSHIBOKEN_API void registerTypes(PyObject *module, PyTypeObject **types); +LIBSHIBOKEN_API void registerTypes(PyObject *module, TypeInitStruct *types); /** * Retrieves the array of types. * \param module Module where the types were created. * \returns A pointer to the PyTypeObject *array of types. */ -LIBSHIBOKEN_API PyTypeObject **getTypes(PyObject *module); +LIBSHIBOKEN_API TypeInitStruct *getTypes(PyObject *module); /** * Registers the list of converters created by \p module for non-wrapper types. diff --git a/sources/shiboken6/libshiboken/sbknumpy.cpp b/sources/shiboken6/libshiboken/sbknumpy.cpp index 060a2b6f..b6422e73 100644 --- a/sources/shiboken6/libshiboken/sbknumpy.cpp +++ b/sources/shiboken6/libshiboken/sbknumpy.cpp @@ -17,9 +17,27 @@ namespace Shiboken::Numpy { +#ifdef HAVE_NUMPY +static void initNumPy() +{ + // PYSIDE-2404: Delay-initialize numpy from check() as it causes a + // significant startup delay (~770 allocations in memray) + static bool initialized = false; + if (initialized) + return; + initialized = true; + // Expanded from macro "import_array" in __multiarray_api.h + // Make sure to read about the magic defines PY_ARRAY_UNIQUE_SYMBOL etc., + // when changing this or spreading the code over several source files. + if (_import_array() < 0) + PyErr_Print(); +} +#endif // HAVE_NUMPY + bool check(PyObject *pyIn) { #ifdef HAVE_NUMPY + initNumPy(); return PyArray_Check(pyIn); #else SBK_UNUSED(pyIn); diff --git a/sources/shiboken6/libshiboken/sbknumpyarrayconverter.cpp b/sources/shiboken6/libshiboken/sbknumpyarrayconverter.cpp index e71fe9d6..835a9752 100644 --- a/sources/shiboken6/libshiboken/sbknumpyarrayconverter.cpp +++ b/sources/shiboken6/libshiboken/sbknumpyarrayconverter.cpp @@ -104,6 +104,7 @@ SbkArrayConverter *unimplementedArrayConverter(); template static bool isPrimitiveArray(PyObject *pyIn, int expectedNpType) { + Shiboken::Numpy::initNumPy(); if (!PyArray_Check(pyIn)) return false; auto *pya = reinterpret_cast(pyIn); @@ -209,6 +210,9 @@ static PythonToCppFunc checkArray2(PyObject *pyIn, int dim1, int dim2) template static void setOrExtendArrayConverter(int dimension, IsArrayConvertibleToCppFunc toCppCheckFunc) { + // PYSIDE-2404/FIXME: When adding a C++ -> Python conversion, be sure + // to delay-initialize numpy in the converter (similar to the + // initialization in check() for the Python -> C++ conversion). SbkArrayConverter *arrayConverter = ArrayTypeConverter(dimension); if (arrayConverter == unimplementedArrayConverter()) { arrayConverter = createArrayConverter(toCppCheckFunc); @@ -234,15 +238,6 @@ static inline void extendArrayConverter2() void initNumPyArrayConverters() { - // Expanded from macro "import_array" in __multiarray_api.h - // Make sure to read about the magic defines PY_ARRAY_UNIQUE_SYMBOL etc., - // when changing this or spreading the code over several source files. - if (_import_array() < 0) { - if (debugNumPy) - PyErr_Print(); - PyErr_Clear(); - return; - } // Extend the converters for primitive types by NumPy ones. extendArrayConverter1(); extendArrayConverter2(); diff --git a/sources/shiboken6/libshiboken/sbkstaticstrings.cpp b/sources/shiboken6/libshiboken/sbkstaticstrings.cpp index 225ed74e..023de0ea 100644 --- a/sources/shiboken6/libshiboken/sbkstaticstrings.cpp +++ b/sources/shiboken6/libshiboken/sbkstaticstrings.cpp @@ -24,6 +24,7 @@ STATIC_STRING_IMPL(im_self, "im_self") STATIC_STRING_IMPL(loads, "loads") STATIC_STRING_IMPL(multi, "multi") STATIC_STRING_IMPL(name, "name") +STATIC_STRING_IMPL(orig_dict, "orig_dict") STATIC_STRING_IMPL(qApp, "qApp") STATIC_STRING_IMPL(result, "result") STATIC_STRING_IMPL(select_id, "select_id") diff --git a/sources/shiboken6/libshiboken/sbkstaticstrings.h b/sources/shiboken6/libshiboken/sbkstaticstrings.h index 02cc8a7f..017790ee 100644 --- a/sources/shiboken6/libshiboken/sbkstaticstrings.h +++ b/sources/shiboken6/libshiboken/sbkstaticstrings.h @@ -23,6 +23,7 @@ LIBSHIBOKEN_API PyObject *im_self(); LIBSHIBOKEN_API PyObject *loads(); LIBSHIBOKEN_API PyObject *multi(); LIBSHIBOKEN_API PyObject *name(); +LIBSHIBOKEN_API PyObject *orig_dict(); LIBSHIBOKEN_API PyObject *result(); LIBSHIBOKEN_API PyObject *select_id(); LIBSHIBOKEN_API PyObject *value(); diff --git a/sources/shiboken6/libshiboken/sbktypefactory.cpp b/sources/shiboken6/libshiboken/sbktypefactory.cpp index 4699f7c6..079548ee 100644 --- a/sources/shiboken6/libshiboken/sbktypefactory.cpp +++ b/sources/shiboken6/libshiboken/sbktypefactory.cpp @@ -77,7 +77,11 @@ static PyObject *_PyType_FromSpecWithBasesHack(PyType_Spec *spec, } } +#if !defined(Py_LIMITED_API) && PY_VERSION_HEX >= 0x030C0000 + auto *ret = PyType_FromMetaclass(meta, nullptr /*module*/, spec, bases); +#else auto *ret = _PyType_FromSpecWithBases(spec, bases); +#endif if (keepMeta) keepMeta->tp_new = keepNew; diff --git a/sources/shiboken6/libshiboken/shiboken.h b/sources/shiboken6/libshiboken/shiboken.h index 6c0ecbe3..fcf777ae 100644 --- a/sources/shiboken6/libshiboken/shiboken.h +++ b/sources/shiboken6/libshiboken/shiboken.h @@ -11,6 +11,7 @@ #include "gilstate.h" #include "threadstatesaver.h" #include "helper.h" +#include "pyobjectholder.h" #include "sbkarrayconverter.h" #include "sbkconverter.h" #include "sbkenum.h" diff --git a/sources/shiboken6/libshiboken/signature/signature.cpp b/sources/shiboken6/libshiboken/signature/signature.cpp index 814d0cea..3255cb56 100644 --- a/sources/shiboken6/libshiboken/signature/signature.cpp +++ b/sources/shiboken6/libshiboken/signature/signature.cpp @@ -248,6 +248,9 @@ PyObject *get_signature_intern(PyObject *ob, PyObject *modifier) return pyside_tp_get___signature__(ob, modifier); if (Py_TYPE(ob) == &PyWrapperDescr_Type) return pyside_wd_get___signature__(ob, modifier); + // For classmethods we use the simple wrapper description implementation. + if (Py_TYPE(ob) == &PyClassMethodDescr_Type) + return pyside_wd_get___signature__(ob, modifier); return nullptr; } @@ -471,6 +474,8 @@ void FinishSignatureInitialization(PyObject *module, const char *signatures[]) * Still, it is not possible to call init phase 2 from here, * because the import is still running. Do it from Python! */ + init_shibokensupport_module(); + #ifndef PYPY_VERSION static const bool patch_types = true; #else diff --git a/sources/shiboken6/libshiboken/signature/signature_extend.cpp b/sources/shiboken6/libshiboken/signature/signature_extend.cpp index ca560d00..7292f821 100644 --- a/sources/shiboken6/libshiboken/signature/signature_extend.cpp +++ b/sources/shiboken6/libshiboken/signature/signature_extend.cpp @@ -77,7 +77,7 @@ PyObject *pyside_md_get___signature__(PyObject *ob_md, PyObject *modifier) { AutoDecRef func(name_key_to_func(ob_md)); if (func.object() == Py_None) - return Py_None; + Py_RETURN_NONE; if (func.isNull()) Py_FatalError("missing mapping in MethodDescriptor"); return pyside_cf_get___signature__(func, modifier); diff --git a/sources/shiboken6/libshiboken/signature/signature_helper.cpp b/sources/shiboken6/libshiboken/signature/signature_helper.cpp index e9d88305..cf84cfa1 100644 --- a/sources/shiboken6/libshiboken/signature/signature_helper.cpp +++ b/sources/shiboken6/libshiboken/signature/signature_helper.cpp @@ -312,6 +312,15 @@ int _build_func_to_type(PyObject *obtype) auto *type = reinterpret_cast(obtype); AutoDecRef tpDict(PepType_GetDict(type)); auto *dict = tpDict.object(); + + // PYSIDE-2404: Get the original dict for late initialization. + // The dict might have been switched before signature init. + static const auto *pyTypeType_tp_dict = PepType_GetDict(&PyType_Type); + if (Py_TYPE(dict) != Py_TYPE(pyTypeType_tp_dict)) { + tpDict.reset(PyObject_GetAttr(dict, PyName::orig_dict())); + dict = tpDict.object(); + } + PyMethodDef *meth = type->tp_methods; if (meth == nullptr) diff --git a/sources/shiboken6/libshiboken/voidptr.cpp b/sources/shiboken6/libshiboken/voidptr.cpp index ed4f88ad..8bb3f6ac 100644 --- a/sources/shiboken6/libshiboken/voidptr.cpp +++ b/sources/shiboken6/libshiboken/voidptr.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "voidptr.h" +#include "pep384ext.h" #include "sbkconverter.h" #include "basewrapper.h" #include "basewrapper_p.h" @@ -24,8 +25,7 @@ PyObject *SbkVoidPtrObject_new(PyTypeObject *type, PyObject * /* args */, PyObje // like this, actual call forgotten: // SbkVoidPtrObject *self = // reinterpret_cast(type->tp_alloc); - PyObject *ob = type->tp_alloc(type, 0); - auto *self = reinterpret_cast(ob); + auto *self = PepExt_TypeCallAlloc(type, 0); if (self != nullptr) { self->cptr = nullptr; @@ -156,10 +156,9 @@ PyObject *SbkVoidPtrObject_int(PyObject *v) PyObject *toBytes(PyObject *self, PyObject * /* args */) { auto *sbkObject = reinterpret_cast(self); - if (sbkObject->size < 0) { - PyErr_SetString(PyExc_IndexError, "VoidPtr does not have a size set."); - return nullptr; - } + if (sbkObject->size < 0) + return PyErr_Format(PyExc_IndexError, "VoidPtr does not have a size set."); + PyObject *bytes = PyBytes_FromStringAndSize(reinterpret_cast(sbkObject->cptr), sbkObject->size); Py_XINCREF(bytes); diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/feature.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/feature.py index d403c758..7a0871ee 100644 --- a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/feature.py +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/feature.py @@ -1,6 +1,9 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +# flake8: noqa F:821 +# flake8: noqa F:401 + """ __feature__.py (renamed to feature.py) @@ -80,6 +83,7 @@ None to indicate that a normal import should be performed, and All these variables are transparently kept in module `builtins`. """ + def feature_import(name, *args, **kwargs): # PYSIDE-1368: The `__name__` attribute does not need to exist in all modules. # PYSIDE-1398: sys._getframe(1) may not exist when embedding. @@ -110,8 +114,10 @@ def feature_import(name, *args, **kwargs): # Redirect to the original import return None + _is_initialized = False + def __init__(): global _is_initialized if not _is_initialized: @@ -182,6 +188,7 @@ def set_selection(select_id, mod_name=None): sys.modules["PySide6.QtCore"].__init_feature__() return _current_selection(flag) + # The set_section(0) case seems to be unsafe. We will migrate to # use the opaque feature.reset() call in all test cases. def reset(): diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/fix-complaints.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/fix-complaints.py index 3e1d4932..f7190b12 100644 --- a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/fix-complaints.py +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/fix-complaints.py @@ -9,7 +9,6 @@ Run it once after copying a new version. It is idem-potent, unless you are changing messages (what I did, of course :-) . """ -import os import glob from pathlib import Path @@ -24,6 +23,7 @@ offending_words = { utf8_line = "# This Python file uses the following encoding: utf-8\n" marker_line = f"# It has been edited by {Path(__file__).name} .\n" + def patch_file(fname): with fname.open() as f: lines = f.readlines() @@ -41,6 +41,7 @@ def patch_file(fname): with open(fname, "w") as f: f.write("".join(lines)) + def doit(): dirname = Path(__file__).parent patched_files = [] @@ -51,6 +52,7 @@ def doit(): print("Working on", fname) patch_file(fname) + if __name__ == "__main__": doit() diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/errorhandler.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/errorhandler.py index 5b63de45..c2a19efe 100644 --- a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/errorhandler.py +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/errorhandler.py @@ -1,6 +1,8 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +# flake8: noqa E:721 + """ errorhandler.py @@ -19,12 +21,10 @@ This matter will be improved in a later version. """ import collections.abc -import inspect -import sys import typing from shibokensupport.signature import get_signature -from shibokensupport.signature.mapping import update_mapping, namespace +from shibokensupport.signature.mapping import namespace from textwrap import dedent @@ -77,8 +77,8 @@ def seterror_argument(args, func_name, info): if info == "<": msg = f"{func_name}(): not enough arguments" elif info == "0": - msg = (f"{func_name}(): not enough arguments. " - "Note: keyword arguments are only supported for optional parameters.") + msg = (f"{func_name}(): not enough arguments. " + "Note: keyword arguments are only supported for optional parameters.") elif info == ">": msg = f"{func_name}(): too many arguments" elif info.isalnum(): diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/importhandler.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/importhandler.py index dbde18f1..bae26429 100644 --- a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/importhandler.py +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/importhandler.py @@ -34,9 +34,10 @@ def finish_import(module): except Exception as e: name = e.__class__.__qualname__ print(72 * "*") - print(f"Error in deprecated.py, ignored:") + print("Error in deprecated.py, ignored:") print(f" {name}: {e}") + """ A note for people who might think this could be written in pure Python: @@ -62,4 +63,3 @@ module, it is *perhaps* possible to solve that. I tried for a day and then gave up, since the solution is anyway not too nice when __import__ must be overridden. """ -#eof diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/layout.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/layout.py index c271be7f..0e781cbc 100644 --- a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/layout.py +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/layout.py @@ -79,6 +79,7 @@ class SignatureLayout(SimpleNamespace): The only allowed values are '{allowed_values}'. """)) + # The following names are used literally in this module. # This way, we avoid the dict hashing problem. signature = SignatureLayout() @@ -115,7 +116,7 @@ def define_nameless_parameter(): P = inspect.Parameter newname = "NamelessParameter" bases = P.__bases__ - body = dict(P.__dict__) # get rid of mappingproxy + body = dict(P.__dict__) # get rid of mappingproxy if "__slots__" in body: # __slots__ would create duplicates for name in body["__slots__"]: @@ -167,12 +168,13 @@ def make_signature_nameless(signature): signature.parameters[key].__class__ = NamelessParameter -_POSITIONAL_ONLY = inspect._POSITIONAL_ONLY -_POSITIONAL_OR_KEYWORD = inspect._POSITIONAL_OR_KEYWORD -_VAR_POSITIONAL = inspect._VAR_POSITIONAL -_KEYWORD_ONLY = inspect._KEYWORD_ONLY -_VAR_KEYWORD = inspect._VAR_KEYWORD -_empty = inspect._empty +_POSITIONAL_ONLY = inspect._POSITIONAL_ONLY # noqa E:201 +_POSITIONAL_OR_KEYWORD = inspect._POSITIONAL_OR_KEYWORD # noqa E:201 +_VAR_POSITIONAL = inspect._VAR_POSITIONAL # noqa E:201 +_KEYWORD_ONLY = inspect._KEYWORD_ONLY # noqa E:201 +_VAR_KEYWORD = inspect._VAR_KEYWORD # noqa E:201 +_empty = inspect._empty # noqa E:201 + def create_signature(props, key): if not props: @@ -183,9 +185,9 @@ def create_signature(props, key): return list(create_signature(elem, key) for elem in props["multi"]) if type(key) is tuple: - sig_kind, modifier = key + _, modifier = key else: - sig_kind, modifier = key, "signature" + _, modifier = key, "signature" layout = globals()[modifier] # lookup of the modifier in this module if not isinstance(layout, SignatureLayout): @@ -234,8 +236,8 @@ def create_signature(props, key): if kind == _VAR_POSITIONAL: kind = _KEYWORD_ONLY sig = inspect.Signature(params, - return_annotation=annotations.get('return', _empty), - __validate_parameters__=False) + return_annotation=annotations.get('return', _empty), + __validate_parameters__=False) # the special case of nameless parameters if not layout.parameter_names: diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/enum_sig.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/enum_sig.py index d07beb88..5650e2bc 100644 --- a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/enum_sig.py +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/enum_sig.py @@ -14,9 +14,7 @@ by producing a lot of clarity. import inspect import sys import types -import typing from shibokensupport.signature import get_signature as get_sig -from shibokensupport.signature.layout import create_signature """ @@ -106,7 +104,7 @@ class ExactEnumerator(object): return ret if "<" in class_name: # This is happening in QtQuick for some reason: - ## class std::shared_ptr: + # class std::shared_ptr: # We simply skip over this class. return ret bases_list = [] @@ -125,6 +123,7 @@ class ExactEnumerator(object): enums = [] properties = [] signals = [] + attributes = {} for thing_name, thing in class_members: if signal_check(thing): @@ -147,6 +146,13 @@ class ExactEnumerator(object): enums.append((thing_name, type(thing).__qualname__, thing)) elif isinstance(thing, property): properties.append((thing_name, thing)) + # Support attributes that have PySide types as values, + # but we skip the 'staticMetaObject' that needs + # to be defined at a QObject level. + elif "PySide" in str(type(thing)) and "QMetaObject" not in str(type(thing)): + if class_name not in attributes: + attributes[class_name] = {} + attributes[class_name][thing_name] = thing if thing_name in self.collision_candidates: self.collision_track.add(thing_name) @@ -160,8 +166,8 @@ class ExactEnumerator(object): # find out how many functions create a signature sigs = list(_ for _ in functions if get_sig(_[1])) - self.fmt.have_body = bool(subclasses or sigs or properties or enums or init_signature - or signals) + self.fmt.have_body = bool(subclasses or sigs or properties or enums or # noqa W:504 + init_signature or signals or attributes) with self.fmt.klass(class_name, class_str): self.fmt.level += 1 @@ -171,7 +177,7 @@ class ExactEnumerator(object): if len(enums): self.section() for enum_name, enum_class_name, value in enums: - with self.fmt.enum(enum_class_name, enum_name,value.value): + with self.fmt.enum(enum_class_name, enum_name, value.value): pass if hasattr(self.fmt, "signal"): # this is an optional feature @@ -183,6 +189,13 @@ class ExactEnumerator(object): sig_str = str(signal) with self.fmt.signal(sig_class_name, signal_name, sig_str): pass + if hasattr(self.fmt, "attribute"): + if len(attributes): + self.section() + for class_name, attrs in attributes.items(): + for attr_name, attr_value in attrs.items(): + with self.fmt.attribute(attr_name, attr_value): + pass if len(subclasses): self.section() for subclass_name, subclass in subclasses: diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/pyi_generator.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/pyi_generator.py index c4434668..ce12dd6c 100644 --- a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/pyi_generator.py +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/pyi_generator.py @@ -3,6 +3,8 @@ LICENSE_TEXT = """ # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only """ +# flake8: noqa E:402 + """ pyi_generator.py @@ -21,7 +23,6 @@ from pathlib import Path from contextlib import contextmanager from textwrap import dedent -from shiboken6 import Shiboken from shibokensupport.signature.lib.enum_sig import HintingEnumerator from shibokensupport.signature.lib.tool import build_brace_pattern @@ -89,6 +90,7 @@ class Formatter(Writer): pattern = fr"\b Union \s* \[ \s* {brace_pat} \s*, \s* NoneType \s* \]" replace = r"Optional[\1]" optional_searcher = re.compile(pattern, flags=re.VERBOSE) + def optional_replacer(source): return optional_searcher.sub(replace, str(source)) self.optional_replacer = optional_replacer @@ -156,6 +158,12 @@ class Formatter(Writer): self.print(f"{spaces}{enum_name:25}: {class_name} = ... # {hexval}") yield + @contextmanager + def attribute(self, attr_name, attr_value): + spaces = indent * self.level + self.print(f"{spaces}{attr_name:25} = ... # type: {type(attr_value).__qualname__}") + yield + @contextmanager def signal(self, class_name, sig_name, sig_str): spaces = indent * self.level @@ -171,11 +179,13 @@ FROM_IMPORTS = [ (None, ["builtins"]), (None, ["os"]), (None, ["enum"]), + ("collections.abc", ["Iterable"]), ("typing", sorted(typing.__all__)), ("PySide6.QtCore", ["PyClassProperty", "Signal", "SignalInstance"]), ("shiboken6", ["Shiboken"]), ] + def filter_from_imports(from_struct, text): """ Build a reduced new `from` structure (nfs) with found entries, only @@ -190,6 +200,10 @@ def filter_from_imports(from_struct, text): if (f"class {each}(") not in text: if re.search(rf"(\b|@){each}\b([^\s\(:]|\n)", text): lis.append(each) + # Search if a type is present in the return statement + # of function declarations: '... -> here:' + if re.search(rf"->.*{each}.*:", text): + lis.append(each) if not lis: nfs.pop() return nfs @@ -228,12 +242,10 @@ def generate_pyi(import_name, outpath, options): obj = getattr(top, plainname) if import_name != plainname else top if not getattr(obj, "__file__", None) or Path(obj.__file__).is_dir(): raise ModuleNotFoundError(f"We do not accept a namespace as module `{plainname}`") - module = sys.modules[import_name] outfile = io.StringIO() fmt = Formatter(outfile, options) fmt.print(LICENSE_TEXT.strip()) - need_imports = options._pyside_call and not USE_PEP563 if USE_PEP563: fmt.print("from __future__ import annotations") fmt.print() @@ -275,15 +287,12 @@ def generate_pyi(import_name, outpath, options): wr.print(f"from {mod} import {import_args}") wr.print() wr.print() - wr.print("NoneType = type(None)") + wr.print("NoneType: TypeAlias = type[None]") wr.print() else: wr.print(line) if not options.quiet: options.logger.info(f"Generated: {outfilepath}") - # PYSIDE-1735: .pyi files are no longer compatible with Python, because - # enum classes contain ellipsis which a Python enum forbids. - # We will implement tests with Mypy, instead. def main(): @@ -298,11 +307,10 @@ def main(): pyi_generator will try to generate an interface ".pyi". """)) parser.add_argument("module", - help="The full path name of an importable module binary (.pyd, .so)") + help="The full path name of an importable module binary (.pyd, .so)") # noqa E:128 parser.add_argument("--quiet", action="store_true", help="Run quietly") - parser.add_argument("--check", action="store_true", help="Test the output") parser.add_argument("--outpath", - help="the output directory (default = location of module binary)") + help="the output directory (default = location of module binary)") # noqa E:128 options = parser.parse_args() module = options.module outpath = options.outpath @@ -320,6 +328,7 @@ def main(): options.logger = logger generate_pyi(module, outpath, options=options) + if __name__ == "__main__": main() # eof diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/tool.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/tool.py index a7900e6b..979dcf4c 100644 --- a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/tool.py +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/tool.py @@ -44,8 +44,8 @@ def build_brace_pattern(level, separators): ro, rc = round_ = "()" so, sc = square = "[]" - co, cc = curly = "CD" # we insert "{}", later... - ao, ac = angle = "<>" + co, cc = curly = "CD" # noqa E:201 we insert "{}", later... + ao, ac = angle = "<>" # noqa E:201 q2, bs, q1 = '"', "\\", "'" allpat = round_ + square + curly + angle __ = " " @@ -79,8 +79,8 @@ def build_brace_pattern(level, separators): {indent} )* """) for idx in range(level): - pattern = pattern.format(replacer = repeated if idx < level-1 else no_braces_q, - indent = idx * " ", **locals()) + pattern = pattern.format(replacer=repeated if idx < level - 1 else no_braces_q, + indent=idx * " ", **locals()) return pattern.replace("C", "{").replace("D", "}") diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/loader.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/loader.py index 6349253d..fb4c9eec 100644 --- a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/loader.py +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/loader.py @@ -1,6 +1,9 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +# flake8: noqa E:402 +# flake8: noqa F:401 + """ loader.py @@ -30,22 +33,27 @@ import types def pyside_type_init(type_key, sig_strings): return parser.pyside_type_init(type_key, sig_strings) + # name used in signature.cpp def create_signature(props, key): return layout.create_signature(props, key) + # name used in signature.cpp def seterror_argument(args, func_name, info): return errorhandler.seterror_argument(args, func_name, info) + # name used in signature.cpp def make_helptext(func): return errorhandler.make_helptext(func) + # name used in signature.cpp def finish_import(module): return importhandler.finish_import(module) + # name used in signature.cpp def feature_import(*args, **kwds): # don't spend a stack level here for speed and compatibility @@ -53,6 +61,7 @@ def feature_import(*args, **kwds): feature_import = feature.feature_import return feature_import(*args, **kwds) + # name used in signature.cpp def feature_imported(*args, **kwds): # don't spend a stack level here for speed and compatibility @@ -107,6 +116,7 @@ def move_into_pyside_package(): put_into_package(PySide6.support.signature.lib, pyi_generator) put_into_package(PySide6.support.signature.lib, tool) + from shibokensupport.signature import mapping from shibokensupport.signature import errorhandler from shibokensupport.signature import layout @@ -119,7 +129,7 @@ from shibokensupport.signature.lib import tool import enum -post_init = lambda:None # default +post_init = lambda: None # noqa E:731 default if "PySide6" in sys.modules: # We publish everything under "PySide6.support", again. @@ -127,7 +137,7 @@ if "PySide6" in sys.modules: # PYSIDE-1502: Make sure that support can be imported. try: import PySide6.support - except ModuleNotFoundError as e: + except ModuleNotFoundError: print("PySide6.support could not be imported. " "This is a serious configuration error.", file=sys.stderr) raise diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/mapping.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/mapping.py index 1af82e31..944a928e 100644 --- a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/mapping.py +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/mapping.py @@ -212,9 +212,9 @@ type_map.update({ "PyCallable": typing.Callable, "PyObject": object, "PyObject*": object, - "PyArrayObject": ArrayLikeVariable, # numpy - "PyPathLike": typing.Union[str, bytes, os.PathLike], - "PySequence": typing.Iterable, # important for numpy + "PyArrayObject": ArrayLikeVariable, # numpy + "PyPathLike": typing.Union[str, bytes, os.PathLike[str]], + "PySequence": typing.Iterable, # important for numpy "PyTypeObject": type, "QChar": str, "QHash": typing.Dict, @@ -241,6 +241,7 @@ type_map.update({ "qreal": float, "QSet": typing.Set, "QString": str, + "QLatin1String": str, "QStringView": str, "QStringList": StringList, "quint16": int, @@ -522,6 +523,7 @@ def init_PySide6_QtCore(): "QVariant()": Invalid(Variant), "QVariant.Type": type, # not so sure here... "QVariantMap": typing.Dict[str, Variant], + "std.chrono.seconds{5}" : ellipsis, }) try: type_map.update({ @@ -559,6 +561,7 @@ def init_PySide6_QtGui(): "int32_t": int, "HBITMAP": int, "HICON": int, + "HMONITOR": int, "HRGN": int, "QPixmap()": Default("PySide6.QtGui.QPixmap"), # can't create without qApp "QPlatformSurface*": int, # a handle diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/parser.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/parser.py index 1936215d..9b48ab44 100644 --- a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/parser.py +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/parser.py @@ -3,20 +3,17 @@ import ast import enum -import functools import keyword import os import re import sys -import types import typing import warnings from types import SimpleNamespace from shibokensupport.signature.mapping import (type_map, update_mapping, - namespace, _NotCalled, ResultVariable, ArrayLikeVariable) + namespace, _NotCalled, ResultVariable, ArrayLikeVariable) # noqa E:128 from shibokensupport.signature.lib.tool import build_brace_pattern -from shibokensupport import feature _DEBUG = False LIST_KEYWORDS = False @@ -41,8 +38,9 @@ guesses, we provide an entry in 'type_map' that resolves it. In effect, 'type_map' maps text to real Python objects. """ + def _get_flag_enum_option(): - from shiboken6 import (__version_info__ as ver, + from shiboken6 import (__version_info__ as ver, # noqa F:401 __minimum_python_version__ as pyminver, __maximum_python_version__ as pymaxver) @@ -80,6 +78,9 @@ def _get_flag_enum_option(): # PYSIDE-1735: Emit a warning when we should maybe evict forgiveness mode if ver[:2] >= (7, 0): warnings.warn(f"{q} Please drop Enum forgiveness mode in `mangled_type_getattro` ***") + # PYSIDE-2404: Emit a warning when we should drop uppercase offset constants + if ver[:2] >= (7, 0): + warnings.warn(f"{q} Please drop uppercase type offsets in `copyOffsetEnumStream` ***") # normalize the sys attribute setattr(sys, sysname, flag) os.environ[envname] = str(flag) @@ -106,6 +107,7 @@ def dprint(*args, **kw): _cache = {} + def _parse_arglist(argstr): # The following is a split re. The string is broken into pieces which are # between the recognized strings. Because the re has groups, both the @@ -187,7 +189,7 @@ def _handle_instance_fixup(thing): if not match: return thing start, stop = match.start(), match.end() - 1 - pre, func, args = thing[:start], thing[start : stop], thing[stop:] + pre, func, args = thing[:start], thing[start:stop], thing[stop:] if func[0].isupper() or func.startswith("gl") and func[2:3].isupper(): return thing # Now convert this string to snake case. @@ -196,7 +198,7 @@ def _handle_instance_fixup(thing): if char.isupper(): if idx and func[idx - 1].isupper(): # two upper chars are forbidden - return things + return thing snake_func += f"_{char.lower()}" else: snake_func += char @@ -239,12 +241,14 @@ def try_to_guess(thing, valtype): return ret return None + def get_name(thing): if isinstance(thing, type): return getattr(thing, "__qualname__", thing.__name__) else: return thing.__name__ + def _resolve_value(thing, valtype, line): if thing in ("0", "None") and valtype: if valtype.startswith("PySide6.") or valtype.startswith("typing."): @@ -297,7 +301,7 @@ def to_string(thing): dot = "." in str(thing) or m not in (thing.__qualname__, "builtins") name = get_name(thing) ret = m + "." + name if dot else name - assert(eval(ret, globals(), namespace)) + assert (eval(ret, globals(), namespace)) return ret # Note: This captures things from the typing module: return str(thing) @@ -305,8 +309,9 @@ def to_string(thing): matrix_pattern = "PySide6.QtGui.QGenericMatrix" + def handle_matrix(arg): - n, m, typstr = tuple(map(lambda x:x.strip(), arg.split(","))) + n, m, typstr = tuple(map(lambda x: x.strip(), arg.split(","))) assert typstr == "float" result = f"PySide6.QtGui.QMatrix{n}x{m}" return eval(result, globals(), namespace) @@ -334,13 +339,13 @@ def _resolve_type(thing, line, level, var_handler, func_name=None): # Special case: Handle the generic matrices. if contr == matrix_pattern: return handle_matrix(thing) - contr = var_handler(_resolve_type(contr, line, level+1, var_handler)) + contr = var_handler(_resolve_type(contr, line, level + 1, var_handler)) if isinstance(contr, _NotCalled): raise SystemError("Container types must exist:", repr(contr)) contr = to_string(contr) pieces = [] for part in _parse_arglist(thing): - part = var_handler(_resolve_type(part, line, level+1, var_handler)) + part = var_handler(_resolve_type(part, line, level + 1, var_handler)) if isinstance(part, _NotCalled): # fix the tag (i.e. "Missing") by repr part = repr(part) @@ -350,7 +355,7 @@ def _resolve_type(thing, line, level, var_handler, func_name=None): # PYSIDE-1538: Make sure that the eval does not crash. try: return eval(result, globals(), namespace) - except Exception as e: + except Exception: warnings.warn(f"""pyside_type_init:_resolve_type UNRECOGNIZED: {result!r} @@ -429,9 +434,9 @@ def calculate_props(line): props.defaults = defaults props.kwdefaults = {} props.annotations = annotations - props.varnames = varnames = tuple(tup[0] for tup in arglist) + props.varnames = tuple(tup[0] for tup in arglist) funcname = parsed.funcname - shortname = funcname[funcname.rindex(".")+1:] + shortname = funcname[funcname.rindex(".") + 1:] props.name = shortname props.multi = parsed.multi fix_variables(props, line) @@ -477,7 +482,6 @@ def fix_variables(props, line): else: diff -= 1 if retvars: - rvs = [] retvars = list(handle_retvar(rv) if isinstance(rv, ArrayLikeVariable) else rv for rv in retvars) if len(retvars) == 1: diff --git a/sources/shiboken6/shibokenmodule/shibokenmodule.cpp b/sources/shiboken6/shibokenmodule/shibokenmodule.cpp new file mode 100644 index 00000000..b3adfe78 --- /dev/null +++ b/sources/shiboken6/shibokenmodule/shibokenmodule.cpp @@ -0,0 +1,115 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +// @snippet isvalid +bool isValid = Shiboken::Object::isValid(%1, false); +%PYARG_0 = %CONVERTTOPYTHON[bool](isValid); +// @snippet isvalid + +// @snippet wrapinstance +auto *pyType = reinterpret_cast(%2); +if (Shiboken::ObjectType::checkType(pyType)) { + auto *ptr = reinterpret_cast(%1); + if (auto *wrapper = Shiboken::BindingManager::instance().retrieveWrapper(ptr)) { + Py_INCREF(wrapper); + %PYARG_0 = reinterpret_cast(wrapper); + } else { + %PYARG_0 = Shiboken::Object::newObject(pyType, ptr, false, true); + } +} else { + PyErr_SetString(PyExc_TypeError, "You need a shiboken-based type."); +} +// @snippet wrapinstance + +// @snippet getcpppointer +if (Shiboken::Object::checkType(%1)) { + std::vector ptrs = Shiboken::Object::cppPointers(reinterpret_cast(%1)); + %PYARG_0 = PyTuple_New(ptrs.size()); + for (std::size_t i = 0; i < ptrs.size(); ++i) + PyTuple_SET_ITEM(%PYARG_0, i, PyLong_FromVoidPtr(ptrs[i])); +} else { + PyErr_SetString(PyExc_TypeError, "You need a shiboken-based type."); +} +// @snippet getcpppointer + +// @snippet delete +if (Shiboken::Object::checkType(%1)) { + Shiboken::Object::callCppDestructors(reinterpret_cast(%1)); +} else { + PyErr_SetString(PyExc_TypeError, "You need a shiboken-based type."); +} +// @snippet delete + +// @snippet ownedbypython +if (Shiboken::Object::checkType(%1)) { + bool hasOwnership = Shiboken::Object::hasOwnership(reinterpret_cast(%1)); + %PYARG_0 = %CONVERTTOPYTHON[bool](hasOwnership); +} else { + PyErr_SetString(PyExc_TypeError, "You need a shiboken-based type."); +} +// @snippet ownedbypython + +// @snippet createdbypython +if (Shiboken::Object::checkType(%1)) { + bool wasCreatedByPython = Shiboken::Object::wasCreatedByPython(reinterpret_cast(%1)); + %PYARG_0 = %CONVERTTOPYTHON[bool](wasCreatedByPython); +} else { + PyErr_SetString(PyExc_TypeError, "You need a shiboken-based type."); +} +// @snippet createdbypython + +// @snippet disassembleframe +Shiboken::AutoDecRef label(PyObject_Str(%1)); +const char *marker = Shiboken::String::toCString(label); +disassembleFrame(marker); +Py_INCREF(Py_None); +%PYARG_0 = Py_None; +// @snippet disassembleframe + +// @snippet dump +if (!Shiboken::Object::checkType(%1)) { + %PYARG_0 = Shiboken::String::fromCString("Ordinary Python type."); +} else { + std::string str = Shiboken::Object::info(reinterpret_cast(%1)); + %PYARG_0 = Shiboken::String::fromCString(str.c_str()); +} +// @snippet dump + +// @snippet getallvalidwrappers +const auto setAll = Shiboken::BindingManager::instance().getAllPyObjects(); +PyObject* listAll = PyList_New(0); +if (listAll == nullptr) + return nullptr; +for (auto *o : setAll) { + if (o != nullptr) { + if (PyList_Append(listAll, o) != 0) { + Py_DECREF(listAll); + return nullptr; + } + } +} +return listAll; +// @snippet getallvalidwrappers + +// @snippet dumptypegraph +const bool ok = Shiboken::BindingManager::instance().dumpTypeGraph(%1); +%PYARG_0 = %CONVERTTOPYTHON[bool](ok); +// @snippet dumptypegraph + +// @snippet dumpwrappermap +Shiboken::BindingManager::instance().dumpWrapperMap(); +// @snippet dumpwrappermap + +// @snippet init +// Add __version__ and __version_info__ attributes to the module +PyObject* version = PyTuple_New(5); +PyTuple_SET_ITEM(version, 0, PyLong_FromLong(SHIBOKEN_MAJOR_VERSION)); +PyTuple_SET_ITEM(version, 1, PyLong_FromLong(SHIBOKEN_MINOR_VERSION)); +PyTuple_SET_ITEM(version, 2, PyLong_FromLong(SHIBOKEN_MICRO_VERSION)); +PyTuple_SET_ITEM(version, 3, Shiboken::String::fromCString(SHIBOKEN_RELEASE_LEVEL)); +PyTuple_SET_ITEM(version, 4, PyLong_FromLong(SHIBOKEN_SERIAL)); +PyModule_AddObject(module, "__version_info__", version); +PyModule_AddStringConstant(module, "__version__", SHIBOKEN_VERSION); +VoidPtr::addVoidPtrToModule(module); +Shiboken::initShibokenSupport(module); +// @snippet init diff --git a/sources/shiboken6/shibokenmodule/typesystem_shiboken.xml b/sources/shiboken6/shibokenmodule/typesystem_shiboken.xml index de5003d5..aa08a8bb 100644 --- a/sources/shiboken6/shibokenmodule/typesystem_shiboken.xml +++ b/sources/shiboken6/shibokenmodule/typesystem_shiboken.xml @@ -1,13 +1,14 @@ + - - bool isValid = Shiboken::Object::isValid(%1, false); - %PYARG_0 = %CONVERTTOPYTHON[bool](isValid); - + @@ -17,119 +18,48 @@ - - auto *pyType = reinterpret_cast<PyTypeObject *>(%2); - if (Shiboken::ObjectType::checkType(pyType)) { - %PYARG_0 = Shiboken::Object::newObject(pyType, - reinterpret_cast<void *>(%1), - false, true); - } else { - PyErr_SetString(PyExc_TypeError, "You need a shiboken-based type."); - } - + - - if (Shiboken::Object::checkType(%1)) { - std::vector<void*> ptrs = Shiboken::Object::cppPointers(reinterpret_cast<SbkObject *>(%1)); - %PYARG_0 = PyTuple_New(ptrs.size()); - for (std::size_t i = 0; i < ptrs.size(); ++i) - PyTuple_SET_ITEM(%PYARG_0, i, PyLong_FromVoidPtr(ptrs[i])); - } else { - PyErr_SetString(PyExc_TypeError, "You need a shiboken-based type."); - } - + - - if (Shiboken::Object::checkType(%1)) { - Shiboken::Object::callCppDestructors(reinterpret_cast<SbkObject *>(%1)); - } else { - PyErr_SetString(PyExc_TypeError, "You need a shiboken-based type."); - } - + - - if (Shiboken::Object::checkType(%1)) { - bool hasOwnership = Shiboken::Object::hasOwnership(reinterpret_cast<SbkObject *>(%1)); - %PYARG_0 = %CONVERTTOPYTHON[bool](hasOwnership); - } else { - PyErr_SetString(PyExc_TypeError, "You need a shiboken-based type."); - } - + - - if (Shiboken::Object::checkType(%1)) { - bool wasCreatedByPython = Shiboken::Object::wasCreatedByPython(reinterpret_cast<SbkObject *>(%1)); - %PYARG_0 = %CONVERTTOPYTHON[bool](wasCreatedByPython); - } else { - PyErr_SetString(PyExc_TypeError, "You need a shiboken-based type."); - } - + - - Shiboken::AutoDecRef label(PyObject_Str(%1)); - const char *marker = Shiboken::String::toCString(label); - disassembleFrame(marker); - Py_INCREF(Py_None); - %PYARG_0 = Py_None; - + - - if (!Shiboken::Object::checkType(%1)) { - %PYARG_0 = Shiboken::String::fromCString("Ordinary Python type."); - } else { - std::string str = Shiboken::Object::info(reinterpret_cast<SbkObject *>(%1)); - %PYARG_0 = Shiboken::String::fromCString(str.c_str()); - } - + - - const auto setAll = Shiboken::BindingManager::instance().getAllPyObjects(); - PyObject* listAll = PyList_New(0); - if (listAll == nullptr) - return nullptr; + + - for (auto *o : setAll) { - if (o != nullptr) { - if (PyList_Append(listAll, o) != 0) { - Py_DECREF(listAll); - return nullptr; - } - } - } - return listAll; - + + + + + + - - // Add __version__ and __version_info__ attributes to the module - PyObject* version = PyTuple_New(5); - PyTuple_SET_ITEM(version, 0, PyLong_FromLong(SHIBOKEN_MAJOR_VERSION)); - PyTuple_SET_ITEM(version, 1, PyLong_FromLong(SHIBOKEN_MINOR_VERSION)); - PyTuple_SET_ITEM(version, 2, PyLong_FromLong(SHIBOKEN_MICRO_VERSION)); - PyTuple_SET_ITEM(version, 3, Shiboken::String::fromCString(SHIBOKEN_RELEASE_LEVEL)); - PyTuple_SET_ITEM(version, 4, PyLong_FromLong(SHIBOKEN_SERIAL)); - PyModule_AddObject(module, "__version_info__", version); - PyModule_AddStringConstant(module, "__version__", SHIBOKEN_VERSION); - VoidPtr::addVoidPtrToModule(module); - - Shiboken::initShibokenSupport(module); - + diff --git a/sources/shiboken6/tests/dumpcodemodel/main.cpp b/sources/shiboken6/tests/dumpcodemodel/main.cpp index f69d67e4..eb876634 100644 --- a/sources/shiboken6/tests/dumpcodemodel/main.cpp +++ b/sources/shiboken6/tests/dumpcodemodel/main.cpp @@ -48,14 +48,14 @@ static const char *primitiveTypes[] = { "float", "double" }; -static inline QString nameAttribute() { return QStringLiteral("name"); } +constexpr auto nameAttribute = "name"_L1; static void formatXmlClass(QXmlStreamWriter &writer, const ClassModelItem &klass); static void formatXmlEnum(QXmlStreamWriter &writer, const EnumModelItem &en) { writer.writeStartElement(u"enum-type"_s); - writer.writeAttribute(nameAttribute(), en->name()); + writer.writeAttribute(nameAttribute, en->name()); writer.writeEndElement(); } @@ -97,7 +97,7 @@ static void formatXmlClass(QXmlStreamWriter &writer, const ClassModelItem &klass formatXmlLocationComment(writer, klass); writer.writeStartElement(isValueType ? u"value-type"_s : u"object-type"_s); - writer.writeAttribute(nameAttribute(), klass->name()); + writer.writeAttribute(nameAttribute, klass->name()); formatXmlScopeMembers(writer, klass); writer.writeEndElement(); } @@ -118,7 +118,7 @@ static void startXmlNamespace(QXmlStreamWriter &writer, const NamespaceModelItem { formatXmlLocationComment(writer, nsp); writer.writeStartElement(u"namespace-type"_s); - writer.writeAttribute(nameAttribute(), nsp->name()); + writer.writeAttribute(nameAttribute, nsp->name()); } static void formatXmlNamespaceMembers(QXmlStreamWriter &writer, const NamespaceModelItem &nsp) @@ -170,7 +170,7 @@ static void formatXmlOutput(const FileModelItem &dom) QDateTime::currentDateTime().toString(Qt::ISODate)); for (auto p : primitiveTypes) { writer.writeStartElement(u"primitive-type"_s); - writer.writeAttribute(nameAttribute(), QLatin1StringView(p)); + writer.writeAttribute(nameAttribute, QLatin1StringView(p)); writer.writeEndElement(); } formatXmlNamespaceMembers(writer, dom); diff --git a/sources/shiboken6/tests/libother/othermultiplederived.h b/sources/shiboken6/tests/libother/othermultiplederived.h index a8e26538..6165a681 100644 --- a/sources/shiboken6/tests/libother/othermultiplederived.h +++ b/sources/shiboken6/tests/libother/othermultiplederived.h @@ -6,6 +6,7 @@ #include "libothermacros.h" #include "multiple_derived.h" +#include "objecttype.h" #include "virtualmethods.h" class ObjectType; diff --git a/sources/shiboken6/tests/libsample/abstract.cpp b/sources/shiboken6/tests/libsample/abstract.cpp index 648cedcd..0d67d863 100644 --- a/sources/shiboken6/tests/libsample/abstract.cpp +++ b/sources/shiboken6/tests/libsample/abstract.cpp @@ -49,6 +49,18 @@ void Abstract::show(PrintFormat format) const std::cout << '>'; } +void Abstract::virtualWithOutParameter(int &x) const +{ + x = 42; +} + +int Abstract::callVirtualWithOutParameter() const +{ + int x; + virtualWithOutParameter(x); + return x; +} + void Abstract::callVirtualGettingEnum(PrintFormat p) { virtualGettingAEnum(p); diff --git a/sources/shiboken6/tests/libsample/abstract.h b/sources/shiboken6/tests/libsample/abstract.h index 1e62a9c5..4c1b98d9 100644 --- a/sources/shiboken6/tests/libsample/abstract.h +++ b/sources/shiboken6/tests/libsample/abstract.h @@ -74,6 +74,9 @@ public: virtual void hideFunction(HideType *arg) = 0; + virtual void virtualWithOutParameter(int &x) const; + int callVirtualWithOutParameter() const; + protected: virtual const char *className() const { return "Abstract"; } diff --git a/sources/shiboken6/tests/libsample/derived.h b/sources/shiboken6/tests/libsample/derived.h index b7736c37..cf95cb60 100644 --- a/sources/shiboken6/tests/libsample/derived.h +++ b/sources/shiboken6/tests/libsample/derived.h @@ -26,7 +26,7 @@ public: public: void uselessMethod() {} SomeInnerClass operator+(const SomeInnerClass &other) { return other; } - bool operator==(const SomeInnerClass &) { return true; } + bool operator==(const SomeInnerClass &) const { return true; } }; explicit Derived(int id = -1) noexcept; diff --git a/sources/shiboken6/tests/libsample/point.cpp b/sources/shiboken6/tests/libsample/point.cpp index b8630eb1..0a28e877 100644 --- a/sources/shiboken6/tests/libsample/point.cpp +++ b/sources/shiboken6/tests/libsample/point.cpp @@ -34,7 +34,7 @@ void Point::show() const std::cout << "(x: " << m_x << ", y: " << m_y << ")"; } -bool Point::operator==(const Point &other) +bool Point::operator==(const Point &other) const { return m_x == other.m_x && m_y == other.m_y; } diff --git a/sources/shiboken6/tests/libsample/point.h b/sources/shiboken6/tests/libsample/point.h index 59e0236d..7e5d128a 100644 --- a/sources/shiboken6/tests/libsample/point.h +++ b/sources/shiboken6/tests/libsample/point.h @@ -38,7 +38,7 @@ public: // The != operator is not implemented for the purpose of testing // for the absense of the __ne__ method in the Python binding. - bool operator==(const Point &other); + bool operator==(const Point &other) const; Point operator+(const Point &other); Point operator-(const Point &other); diff --git a/sources/shiboken6/tests/libsample/pointf.cpp b/sources/shiboken6/tests/libsample/pointf.cpp index 6b39f73a..736a5c6b 100644 --- a/sources/shiboken6/tests/libsample/pointf.cpp +++ b/sources/shiboken6/tests/libsample/pointf.cpp @@ -26,7 +26,7 @@ void PointF::show() const std::cout << "(x: " << m_x << ", y: " << m_y << ")"; } -bool PointF::operator==(const PointF &other) +bool PointF::operator==(const PointF &other) const { return m_x == other.m_x && m_y == other.m_y; } diff --git a/sources/shiboken6/tests/libsample/pointf.h b/sources/shiboken6/tests/libsample/pointf.h index bb50b5c6..49e00946 100644 --- a/sources/shiboken6/tests/libsample/pointf.h +++ b/sources/shiboken6/tests/libsample/pointf.h @@ -31,7 +31,7 @@ public: // The != operator is not implemented for the purpose of testing // for the absence of the __ne__ method in the Python binding. - bool operator==(const PointF &other); + bool operator==(const PointF &other) const; PointF operator+(const PointF &other); PointF operator-(const PointF &other); diff --git a/sources/shiboken6/tests/libsmart/stdsharedptrtestbench.cpp b/sources/shiboken6/tests/libsmart/stdsharedptrtestbench.cpp index a7b73cc8..1be93db6 100644 --- a/sources/shiboken6/tests/libsmart/stdsharedptrtestbench.cpp +++ b/sources/shiboken6/tests/libsmart/stdsharedptrtestbench.cpp @@ -51,6 +51,26 @@ void StdSharedPtrTestBench::printInt(const std::shared_ptr &p) std::cerr << '\n'; } +std::shared_ptr StdSharedPtrTestBench::createString(const char *text) +{ + return std::make_shared(text); +} + +std::shared_ptr StdSharedPtrTestBench::createNullString() +{ + return {}; +} + +void StdSharedPtrTestBench::printString(const std::shared_ptr &p) +{ + std::cerr << __FUNCTION__ << ' '; + if (p.get()) + std::cerr << '"' << *p << '"'; + else + std::cerr << "nullptr"; + std::cerr << '\n'; +} + StdSharedPtrVirtualMethodTester::StdSharedPtrVirtualMethodTester() = default; StdSharedPtrVirtualMethodTester::~StdSharedPtrVirtualMethodTester() = default; diff --git a/sources/shiboken6/tests/libsmart/stdsharedptrtestbench.h b/sources/shiboken6/tests/libsmart/stdsharedptrtestbench.h index 8991cded..04d75d5e 100644 --- a/sources/shiboken6/tests/libsmart/stdsharedptrtestbench.h +++ b/sources/shiboken6/tests/libsmart/stdsharedptrtestbench.h @@ -7,6 +7,7 @@ #include "libsmartmacros.h" #include +#include class Integer; @@ -23,6 +24,10 @@ public: static std::shared_ptr createInt(int v = 42); static std::shared_ptr createNullInt(); static void printInt(const std::shared_ptr &); + + static std::shared_ptr createString(const char *text); + static std::shared_ptr createNullString(); + static void printString(const std::shared_ptr &); }; class LIB_SMART_API StdSharedPtrVirtualMethodTester diff --git a/sources/shiboken6/tests/minimalbinding/brace_pattern_test.py b/sources/shiboken6/tests/minimalbinding/brace_pattern_test.py index f625fc77..946a869d 100644 --- a/sources/shiboken6/tests/minimalbinding/brace_pattern_test.py +++ b/sources/shiboken6/tests/minimalbinding/brace_pattern_test.py @@ -11,7 +11,7 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from shiboken_paths import init_paths init_paths() -from shiboken6 import Shiboken +from shiboken6 import Shiboken # noqa: F401 from shibokensupport.signature.lib.tool import build_brace_pattern @@ -21,8 +21,9 @@ against a slower reference implementation. The pattern is crucial, because it is used heavily in signature.parser . """ -# A slow reference parser for braces and strings + def check(s): + """A slow reference parser for braces and strings""" open, close = "[{(<", "]})>" escape, quote = "\\", '"' instring = blind = False @@ -41,8 +42,7 @@ def check(s): stack.append(c) elif c in close: pos = close.index(c) - if ((len(stack) > 0) and - (open[pos] == stack[len(stack)-1])): + if len(stack) > 0 and open[pos] == stack[len(stack) - 1]: stack.pop() else: return False diff --git a/sources/shiboken6/tests/minimalbinding/listuser_test.py b/sources/shiboken6/tests/minimalbinding/listuser_test.py index 4d092b33..b30bb653 100644 --- a/sources/shiboken6/tests/minimalbinding/listuser_test.py +++ b/sources/shiboken6/tests/minimalbinding/listuser_test.py @@ -32,7 +32,7 @@ class ExtListUser(ListUser): return [not mb1, not mb2] def oredMinBoolList(self, minBoolList): - return not reduce(lambda a, b: a|b, minBoolList) + return not reduce(lambda a, b: a | b, minBoolList) def createValList(self, num): return [Val(i) for i in range(0, num * 2, 2)] @@ -303,14 +303,16 @@ class ListOfIntListConversionTest(unittest.TestCase): def testSumListOfIntListsFromExtendedClass(self): lu = ExtListUser() lst = [range(4)] * 4 - self.assertEqual(lu.sumListOfIntLists(lst), sum([sum(line) for line in [range(4)] * 4]) * 2) - self.assertEqual(lu.callSumListOfIntLists(lst), sum([sum(line) for line in [range(4)] * 4]) * 2) + self.assertEqual(lu.sumListOfIntLists(lst), + sum([sum(line) for line in [range(4)] * 4]) * 2) + self.assertEqual(lu.callSumListOfIntLists(lst), + sum([sum(line) for line in [range(4)] * 4]) * 2) def testOpaqueContainer(self): lu = ListUser() # Set via Python - python_list = [1,2] + python_list = [1, 2] lu.setStdIntList(python_list) self.assertEqual(len(lu.m_stdIntList), 2) self.assertEqual(lu.m_stdIntList[0], 1) @@ -331,8 +333,8 @@ class ListOfIntListConversionTest(unittest.TestCase): self.assertEqual(lu.m_stdIntList[2], 5) # Access list via getter - l = lu.getIntList() - l.append(6) + il = lu.getIntList() + il.append(6) self.assertEqual(len(lu.m_stdIntList), 4) self.assertEqual(lu.m_stdIntList[3], 6) diff --git a/sources/shiboken6/tests/minimalbinding/minbool_test.py b/sources/shiboken6/tests/minimalbinding/minbool_test.py index 331b410b..d9ce0eac 100644 --- a/sources/shiboken6/tests/minimalbinding/minbool_test.py +++ b/sources/shiboken6/tests/minimalbinding/minbool_test.py @@ -13,32 +13,34 @@ init_paths() from minimal import MinBoolUser + class DerivedMinBoolUser (MinBoolUser): def returnMyselfVirtual(self): return MinBoolUser() + class MinBoolTest(unittest.TestCase): def testMinBoolUser(self): mbuTrue = MinBoolUser() mbuFalse = MinBoolUser() mbuTrue.setMinBool(True) - self.assertEqual(mbuFalse.minBool(), False) - self.assertEqual(mbuTrue.minBool(), True) - self.assertEqual(mbuTrue.callInvertedMinBool(), False) + self.assertFalse(mbuFalse.minBool()) + self.assertTrue(mbuTrue.minBool()) + self.assertFalse(mbuTrue.callInvertedMinBool()) - self.assertEqual(mbuTrue.minBool() == True, True) - self.assertEqual(False == mbuFalse.minBool(), True) - self.assertEqual(mbuTrue.minBool() == mbuFalse.minBool(), False) + self.assertTrue(mbuTrue.minBool()) + self.assertFalse(mbuFalse.minBool()) + self.assertTrue(mbuTrue.minBool() != mbuFalse.minBool()) - self.assertEqual(mbuFalse.minBool() != True, True) - self.assertEqual(True != mbuFalse.minBool(), True) - self.assertEqual(mbuTrue.minBool() != mbuFalse.minBool(), True) + self.assertFalse(mbuFalse.minBool()) + self.assertFalse(mbuFalse.minBool()) + self.assertTrue(mbuTrue.minBool() != mbuFalse.minBool()) def testVirtuals(self): dmbu = DerivedMinBoolUser() self.assertEqual(dmbu.invertedMinBool(), True) + if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/minimalbinding/obj_test.py b/sources/shiboken6/tests/minimalbinding/obj_test.py index 0d8a2dce..e873845d 100644 --- a/sources/shiboken6/tests/minimalbinding/obj_test.py +++ b/sources/shiboken6/tests/minimalbinding/obj_test.py @@ -12,6 +12,7 @@ from shiboken_paths import init_paths init_paths() from minimal import Obj + class ExtObj(Obj): def __init__(self, objId): Obj.__init__(self, objId) @@ -91,4 +92,3 @@ class ObjTest(unittest.TestCase): if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/minimalbinding/typedef_test.py b/sources/shiboken6/tests/minimalbinding/typedef_test.py index 1d878be3..c2fc8fc1 100644 --- a/sources/shiboken6/tests/minimalbinding/typedef_test.py +++ b/sources/shiboken6/tests/minimalbinding/typedef_test.py @@ -2,7 +2,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -from functools import reduce import os import sys import unittest @@ -11,7 +10,9 @@ from pathlib import Path sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from shiboken_paths import init_paths init_paths() -from minimal import * +from minimal import (arrayFunc, arrayFuncInt, arrayFuncIntReturn, + arrayFuncIntReturnTypedef, arrayFuncIntTypedef, + arrayFuncReturn, arrayFuncReturnTypedef, arrayFuncTypedef) try: import numpy as np @@ -31,29 +32,37 @@ class TypedefTest(unittest.TestCase): self.assertTrue(arrayFuncInt(none), "None is empty, arrayFuncInt should return true") self.assertFalse(arrayFuncInt(full), "Full is NOT empty, arrayFuncInt should return false") - self.assertTrue(arrayFuncInt(np.array(none)), "None is empty, arrayFuncInt should return true") - self.assertFalse(arrayFuncInt(np.array(full)), "Full is NOT empty, arrayFuncInt should return false") + self.assertTrue(arrayFuncInt(np.array(none)), + "None is empty, arrayFuncInt should return true") + self.assertFalse(arrayFuncInt(np.array(full)), + "Full is NOT empty, arrayFuncInt should return false") def test_arrayFuncIntTypedef(self): none = () full = (1, 2, 3) - self.assertTrue(arrayFuncIntTypedef(none), "None is empty, arrayFuncIntTypedef should return true") - self.assertFalse(arrayFuncIntTypedef(full), "Full is NOT empty, arrayFuncIntTypedef should return false") + self.assertTrue(arrayFuncIntTypedef(none), + "None is empty, arrayFuncIntTypedef should return true") + self.assertFalse(arrayFuncIntTypedef(full), + "Full is NOT empty, arrayFuncIntTypedef should return false") - self.assertTrue(arrayFuncIntTypedef(np.array(none)), "None is empty, arrayFuncIntTypedef should return true") - self.assertFalse(arrayFuncIntTypedef(np.array(full)), "Full is NOT empty, arrayFuncIntTypedef should return false") + self.assertTrue(arrayFuncIntTypedef(np.array(none)), + "None is empty, arrayFuncIntTypedef should return true") + self.assertFalse(arrayFuncIntTypedef(np.array(full)), + "Full is NOT empty, arrayFuncIntTypedef should return false") def test_arrayFuncIntReturn(self): none = arrayFuncIntReturn(0) full = arrayFuncIntReturn(self.the_size) self.assertTrue((len(none) == 0), "none should be empty") - self.assertTrue((len(full) == self.the_size), "full should have " + str(self.the_size) + " elements") + self.assertTrue((len(full) == self.the_size), + f"full should have {self.the_size} elements") def test_arrayFuncIntReturnTypedef(self): none = arrayFuncIntReturnTypedef(0) full = arrayFuncIntReturnTypedef(self.the_size) self.assertTrue((len(none) == 0), "none should be empty") - self.assertTrue((len(full) == self.the_size), "full should have " + str(self.the_size) + " elements") + self.assertTrue((len(full) == self.the_size), + f"full should have {self.the_size} elements") def test_arrayFunc(self): none = () @@ -62,30 +71,37 @@ class TypedefTest(unittest.TestCase): self.assertFalse(arrayFunc(full), "Full is NOT empty, arrayFunc should return false") self.assertTrue(arrayFunc(np.array(none)), "None is empty, arrayFunc should return true") - self.assertFalse(arrayFunc(np.array(full)), "Full is NOT empty, arrayFunc should return false") + self.assertFalse(arrayFunc(np.array(full)), + "Full is NOT empty, arrayFunc should return false") def test_arrayFuncTypedef(self): none = () full = (1, 2, 3) - self.assertTrue(arrayFuncTypedef(none), "None is empty, arrayFuncTypedef should return true") - self.assertFalse(arrayFuncTypedef(full), "Full is NOT empty, arrayFuncTypedef should return false") + self.assertTrue(arrayFuncTypedef(none), + "None is empty, arrayFuncTypedef should return true") + self.assertFalse(arrayFuncTypedef(full), + "Full is NOT empty, arrayFuncTypedef should return false") - self.assertTrue(arrayFuncTypedef(np.array(none)), "None is empty, arrayFuncTypedef should return true") - self.assertFalse(arrayFuncTypedef(np.array(full)), "Full is NOT empty, arrayFuncTypedef should return false") + self.assertTrue(arrayFuncTypedef(np.array(none)), + "None is empty, arrayFuncTypedef should return true") + self.assertFalse(arrayFuncTypedef(np.array(full)), + "Full is NOT empty, arrayFuncTypedef should return false") def test_arrayFuncReturn(self): none = arrayFuncReturn(0) full = arrayFuncReturn(self.the_size) self.assertTrue((len(none) == 0), "none should be empty") - self.assertTrue((len(full) == self.the_size), "full should have " + str(self.the_size) + " elements") + self.assertTrue((len(full) == self.the_size), + f"full should have {self.the_size} elements") def test_arrayFuncReturnTypedef(self): none = arrayFuncReturnTypedef(0) full = arrayFuncReturnTypedef(self.the_size) self.assertTrue((len(none) == 0), "none should be empty") - self.assertTrue((len(full) == self.the_size), "full should have " + str(self.the_size) + " elements") + self.assertTrue((len(full) == self.the_size), + f"full should have {self.the_size} elements") if __name__ == '__main__': - if np != None: + if np is not None: unittest.main() diff --git a/sources/shiboken6/tests/minimalbinding/val_test.py b/sources/shiboken6/tests/minimalbinding/val_test.py index 6403b5f1..b8225a24 100644 --- a/sources/shiboken6/tests/minimalbinding/val_test.py +++ b/sources/shiboken6/tests/minimalbinding/val_test.py @@ -92,4 +92,3 @@ class ValTest(unittest.TestCase): if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/otherbinding/collector_external_operator_test.py b/sources/shiboken6/tests/otherbinding/collector_external_operator_test.py index ab9a6734..2ba21653 100644 --- a/sources/shiboken6/tests/otherbinding/collector_external_operator_test.py +++ b/sources/shiboken6/tests/otherbinding/collector_external_operator_test.py @@ -16,6 +16,7 @@ init_paths() from sample import Collector, ObjectType from other import OtherObjectType + class CollectorOtherObjectType(unittest.TestCase): '''Test cases for Collector << OtherObjectType''' @@ -33,6 +34,6 @@ class CollectorOtherObjectType(unittest.TestCase): collector << obj self.assertEqual(collector.items()[0], obj.identifier() * 2) + if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/otherbinding/conversion_operator_for_class_without_implicit_conversions_test.py b/sources/shiboken6/tests/otherbinding/conversion_operator_for_class_without_implicit_conversions_test.py index 220cd3c9..bd00b589 100644 --- a/sources/shiboken6/tests/otherbinding/conversion_operator_for_class_without_implicit_conversions_test.py +++ b/sources/shiboken6/tests/otherbinding/conversion_operator_for_class_without_implicit_conversions_test.py @@ -18,8 +18,10 @@ init_paths() from sample import NoImplicitConversion from other import ExtendsNoImplicitConversion + class ConversionOperatorForClassWithoutImplicitConversionsTest(unittest.TestCase): - '''Tests calling NoImplicitConversion constructor using a ExtendsNoImplicitConversion parameter.''' + '''Tests calling NoImplicitConversion constructor using a + ExtendsNoImplicitConversion parameter.''' def testNoImplicitConversion(self): '''Basic test to see if the NoImplicitConversion is Ok.''' @@ -27,27 +29,33 @@ class ConversionOperatorForClassWithoutImplicitConversionsTest(unittest.TestCase # NoImplicitConversion.receivesNoImplicitConversionByValue(NoImplicitConversion) self.assertEqual(obj.objId(), NoImplicitConversion.receivesNoImplicitConversionByValue(obj)) # NoImplicitConversion.receivesNoImplicitConversionByPointer(NoImplicitConversion*) - self.assertEqual(obj.objId(), NoImplicitConversion.receivesNoImplicitConversionByPointer(obj)) + self.assertEqual(obj.objId(), + NoImplicitConversion.receivesNoImplicitConversionByPointer(obj)) # NoImplicitConversion.receivesNoImplicitConversionByReference(NoImplicitConversion&) - self.assertEqual(obj.objId(), NoImplicitConversion.receivesNoImplicitConversionByReference(obj)) + self.assertEqual(obj.objId(), + NoImplicitConversion.receivesNoImplicitConversionByReference(obj)) def testPassingExtendsNoImplicitConversionAsNoImplicitConversionByValue(self): - '''Gives an ExtendsNoImplicitConversion object to a function expecting a NoImplicitConversion, passing by value.''' + '''Gives an ExtendsNoImplicitConversion object to a function expecting a + NoImplicitConversion, passing by value.''' obj = ExtendsNoImplicitConversion(123) self.assertEqual(obj.objId(), NoImplicitConversion.receivesNoImplicitConversionByValue(obj)) def testPassingExtendsNoImplicitConversionAsNoImplicitConversionByReference(self): - '''Gives an ExtendsNoImplicitConversion object to a function expecting a NoImplicitConversion, passing by reference.''' + '''Gives an ExtendsNoImplicitConversion object to a function expecting a + NoImplicitConversion, passing by reference.''' obj = ExtendsNoImplicitConversion(123) - self.assertEqual(obj.objId(), NoImplicitConversion.receivesNoImplicitConversionByReference(obj)) + self.assertEqual(obj.objId(), + NoImplicitConversion.receivesNoImplicitConversionByReference(obj)) def testPassingExtendsNoImplicitConversionAsNoImplicitConversionByPointer(self): - '''Gives an ExtendsNoImplicitConversion object to a function expecting a NoImplicitConversion, passing by pointer. - This should not be accepted, since pointers should not be converted.''' + '''Gives an ExtendsNoImplicitConversion object to a function expecting + a NoImplicitConversion, passing by pointer. This should not be + accepted, since pointers should not be converted.''' obj = ExtendsNoImplicitConversion(123) - self.assertRaises(TypeError, NoImplicitConversion.receivesNoImplicitConversionByPointer, obj) + self.assertRaises(TypeError, + NoImplicitConversion.receivesNoImplicitConversionByPointer, obj) if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/otherbinding/extended_multiply_operator_test.py b/sources/shiboken6/tests/otherbinding/extended_multiply_operator_test.py index 169f5259..abbef623 100644 --- a/sources/shiboken6/tests/otherbinding/extended_multiply_operator_test.py +++ b/sources/shiboken6/tests/otherbinding/extended_multiply_operator_test.py @@ -16,6 +16,7 @@ init_paths() from sample import Point from other import Number + class PointOperationsWithNumber(unittest.TestCase): '''Test cases for libsample's Point multiply operator defined in libother module.''' @@ -39,6 +40,6 @@ class PointOperationsWithNumber(unittest.TestCase): num = Number(11) self.assertEqual(pt * num.value(), pt * 11) + if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/otherbinding/module_reload_test.py b/sources/shiboken6/tests/otherbinding/module_reload_test.py index 5be2b419..bde2f523 100644 --- a/sources/shiboken6/tests/otherbinding/module_reload_test.py +++ b/sources/shiboken6/tests/otherbinding/module_reload_test.py @@ -21,6 +21,7 @@ dst = workdir / 'test_module.py' shutil.copyfile(src, dst) sys.path.append(os.fspath(workdir)) + class TestModuleReloading(unittest.TestCase): def testModuleReloading(self): @@ -32,5 +33,6 @@ class TestModuleReloading(unittest.TestCase): reload(test_module) self.assertFalse(oldObject is test_module.obj) + if __name__ == "__main__": unittest.main() diff --git a/sources/shiboken6/tests/otherbinding/new_ctor_operator_test.py b/sources/shiboken6/tests/otherbinding/new_ctor_operator_test.py index 5e3536c9..d6c35643 100644 --- a/sources/shiboken6/tests/otherbinding/new_ctor_operator_test.py +++ b/sources/shiboken6/tests/otherbinding/new_ctor_operator_test.py @@ -2,7 +2,8 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -'''Tests calling Str constructor using a Number parameter, being that number defines a cast operator to Str.''' +'''Tests calling Str constructor using a Number parameter, being that number defines + a cast operator to Str.''' import os import sys @@ -16,8 +17,10 @@ init_paths() from sample import Str from other import Number + class NewCtorOperatorTest(unittest.TestCase): - '''Tests calling Str constructor using a Number parameter, being that number defines a cast operator to Str.''' + '''Tests calling Str constructor using a Number parameter, being that number + defines a cast operator to Str.''' def testNumber(self): '''Basic test to see if the Number class is Ok.''' @@ -29,8 +32,8 @@ class NewCtorOperatorTest(unittest.TestCase): '''Try to build a Str from 'sample' module with a Number argument from 'other' module.''' value = 123 num = Number(value) - string = Str(num) + string = Str(num) # noqa: F841 + if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/otherbinding/objtypehashes_test.py b/sources/shiboken6/tests/otherbinding/objtypehashes_test.py index d2c44105..d2cd7de5 100644 --- a/sources/shiboken6/tests/otherbinding/objtypehashes_test.py +++ b/sources/shiboken6/tests/otherbinding/objtypehashes_test.py @@ -9,10 +9,10 @@ from pathlib import Path sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from shiboken_paths import init_paths init_paths() -from sample import * -from other import * +from sample import HandleHolder from shiboken6 import Shiboken + class TestHashFuncs (unittest.TestCase): def testIt(self): @@ -30,6 +30,5 @@ class TestHashFuncs (unittest.TestCase): self.assertEqual(hash1_2, hash1) - if __name__ == '__main__': unittest.main() diff --git a/sources/shiboken6/tests/otherbinding/otherderived_test.py b/sources/shiboken6/tests/otherbinding/otherderived_test.py index 1b876e1e..459f474f 100644 --- a/sources/shiboken6/tests/otherbinding/otherderived_test.py +++ b/sources/shiboken6/tests/otherbinding/otherderived_test.py @@ -17,6 +17,7 @@ init_paths() from sample import Abstract, Derived from other import OtherDerived, Number + class Multiple(Derived, Number): def __init__(self): Derived.__init__(self, 42) @@ -25,6 +26,7 @@ class Multiple(Derived, Number): def testCall(self): return True + class OtherDeviant(OtherDerived): def __init__(self): OtherDerived.__init__(self) @@ -40,6 +42,7 @@ class OtherDeviant(OtherDerived): def className(self): return 'OtherDeviant' + class MultipleTest(unittest.TestCase): '''Test case for Multiple derived class''' @@ -58,6 +61,7 @@ class MultipleTest(unittest.TestCase): self.assertTrue(o.value(), 42) self.assertTrue(o.testCall()) + class OtherDerivedTest(unittest.TestCase): '''Test case for OtherDerived class''' @@ -68,13 +72,15 @@ class OtherDerivedTest(unittest.TestCase): self.assertTrue(inherited_methods.issubset(dir(OtherDerived))) def testReimplementedPureVirtualMethodCall(self): - '''Test if a Python override of a implemented pure virtual method is correctly called from C++.''' + '''Test if a Python override of a implemented pure virtual method is + correctly called from C++.''' d = OtherDeviant() d.callPureVirtual() self.assertTrue(d.pure_virtual_called) def testReimplementedVirtualMethodCall(self): - '''Test if a Python override of a reimplemented virtual method is correctly called from C++.''' + '''Test if a Python override of a reimplemented virtual method is + correctly called from C++.''' d = OtherDeviant() d.callUnpureVirtual() self.assertTrue(d.unpure_virtual_called) @@ -86,7 +92,8 @@ class OtherDerivedTest(unittest.TestCase): self.assertEqual(d.getClassName(), 'OtherDerived') def testReimplementedVirtualMethodCallReturningString(self): - '''Test if a Python override of a reimplemented virtual method is correctly called from C++.''' + '''Test if a Python override of a reimplemented virtual method is + correctly called from C++.''' d = OtherDeviant() self.assertEqual(d.className(), 'OtherDeviant') self.assertEqual(d.getClassName(), 'OtherDeviant') @@ -97,6 +104,6 @@ class OtherDerivedTest(unittest.TestCase): d = OtherDerived(objId) self.assertEqual(Abstract.getObjectId(d), objId) + if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/otherbinding/othertypesystypedef_test.py b/sources/shiboken6/tests/otherbinding/othertypesystypedef_test.py index 9ac2456c..198c7169 100644 --- a/sources/shiboken6/tests/otherbinding/othertypesystypedef_test.py +++ b/sources/shiboken6/tests/otherbinding/othertypesystypedef_test.py @@ -13,8 +13,7 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from shiboken_paths import init_paths init_paths() -from other import (OtherValueWithUnitUser, ValueWithUnitIntInch, - ValueWithUnitIntMillimeter) +from other import (OtherValueWithUnitUser, ValueWithUnitIntMillimeter) from sample import (ValueWithUnitDoubleMillimeter) diff --git a/sources/shiboken6/tests/otherbinding/signature_test.py b/sources/shiboken6/tests/otherbinding/signature_test.py index 93f080e6..8db3e566 100644 --- a/sources/shiboken6/tests/otherbinding/signature_test.py +++ b/sources/shiboken6/tests/otherbinding/signature_test.py @@ -16,8 +16,6 @@ init_paths() from other import OtherObjectType from shiboken_test_helper import objectFullname -from shiboken6 import Shiboken - from shibokensupport.signature import get_signature @@ -29,7 +27,7 @@ class SignatureTest(unittest.TestCase): def testNamespaceFromOtherModule(self): argType = get_signature(OtherObjectType.enumAsInt).parameters["value"].annotation self.assertEqual(objectFullname(argType), - "sample.SampleNamespace.SomeClass.PublicScopedEnum") + "sample.SampleNamespace.SomeClass.PublicScopedEnum") if __name__ == '__main__': diff --git a/sources/shiboken6/tests/otherbinding/smartptr_test.py b/sources/shiboken6/tests/otherbinding/smartptr_test.py index 6479e633..fd5c7fa0 100644 --- a/sources/shiboken6/tests/otherbinding/smartptr_test.py +++ b/sources/shiboken6/tests/otherbinding/smartptr_test.py @@ -13,8 +13,6 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from shiboken_paths import init_paths init_paths() -from smart import Integer -from sample import Str from other import SmartPtrTester diff --git a/sources/shiboken6/tests/otherbinding/star_import_test.py b/sources/shiboken6/tests/otherbinding/star_import_test.py new file mode 100644 index 00000000..4b5f1d27 --- /dev/null +++ b/sources/shiboken6/tests/otherbinding/star_import_test.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +"""PYSIDE-2404: Test whether star imports work as they require special handling + by the lazy initialization.""" + +import os +import sys +import unittest + +from pathlib import Path +sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) +from shiboken_paths import init_paths +init_paths() + +SHIBOKEN_NAME = "shiboken6.Shiboken" +MINIMAL_NAME = "minimal" +OTHER_NAME = "other" + +shiboken_loaded = 1 if sys.modules.get(SHIBOKEN_NAME) else 0 +minimal_loaded = 1 if sys.modules.get(MINIMAL_NAME) else 0 +other_loaded = 1 if sys.modules.get(OTHER_NAME) else 0 + +from minimal import * # noqa: F403 + +shiboken_loaded += 2 if sys.modules.get(SHIBOKEN_NAME) else 0 +minimal_loaded += 2 if sys.modules.get(MINIMAL_NAME) else 0 +other_loaded += 2 if sys.modules.get(OTHER_NAME) else 0 + +from other import Number # noqa: F403 +from other import * # noqa: F403 + +shiboken_loaded += 4 if sys.modules.get(SHIBOKEN_NAME) else 0 +minimal_loaded += 4 if sys.modules.get(MINIMAL_NAME) else 0 +other_loaded = +4 if sys.modules.get(OTHER_NAME) else 0 + +import shiboken6.Shiboken # noqa: F401 F403 + +shiboken_loaded += 8 if sys.modules.get(SHIBOKEN_NAME) else 0 + + +class ValTest(unittest.TestCase): + + def test(self): + val_id = 123 + val = Val(val_id) # noqa: F405 + self.assertEqual(val.valId(), val_id) + + +class Simple(Number): + + def __init__(self): + Number.__init__(self, 42) + + +class OtherTest(unittest.TestCase): + + def testConstructor(self): + o = Simple() + self.assertTrue(isinstance(o, Number)) + + +class StarImportTest(unittest.TestCase): + """ + This test is meant for Lazy Init. + We explicitly choose modules which are able to lazy load. + + The ValTest: + ------------ + We load something with `import *`. + There is no module from our known ones imported. + This means we need stack introspection to find out that this was + a star import and we must disable lazyness. + + The OtherTest: + -------------- + We load something normally that should be lazy. + After that, we follow with a star import. + Now the stack introspection does not work, because the loading is + cached. The first import did a lazy load. The following star import + needs to undo the lazyness. But now we have a redirected import. + + All tests simply check if the objects are real and not just names. + The _loaded tests prevend upcoming internal dependencies. + + To make sure that Shiboken is really not involved, it is checked + and really imported afterwards (ensuring nothing is misspelled). + """ + + def testStar(self): + self.assertEqual(other_loaded, 4) + self.assertEqual(minimal_loaded, 6) + self.assertEqual(shiboken_loaded, 14) + # Interesting effect: Did not expect that shiboken is loaded at all. + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/shiboken6/tests/otherbinding/test_module_template.py b/sources/shiboken6/tests/otherbinding/test_module_template.py index 183ba9fe..36ab43ae 100644 --- a/sources/shiboken6/tests/otherbinding/test_module_template.py +++ b/sources/shiboken6/tests/otherbinding/test_module_template.py @@ -1,13 +1,22 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -from other import * -from sample import * +import os +import sys + +from pathlib import Path +sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) +from shiboken_paths import init_paths +init_paths() + +from other import OtherObjectType +from sample import ObjectType class MyObjectType(ObjectType): pass + class MyOtherObjectType(OtherObjectType): value = 10 diff --git a/sources/shiboken6/tests/otherbinding/typediscovery_test.py b/sources/shiboken6/tests/otherbinding/typediscovery_test.py index 51fc7c01..791d3bdc 100644 --- a/sources/shiboken6/tests/otherbinding/typediscovery_test.py +++ b/sources/shiboken6/tests/otherbinding/typediscovery_test.py @@ -13,9 +13,10 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from shiboken_paths import init_paths init_paths() -from sample import Abstract, Base1, Derived, MDerived1, MDerived3, SonOfMDerived1 +from sample import Abstract, Base1, Derived from other import OtherMultipleDerived + class TypeDiscoveryTest(unittest.TestCase): def testPureVirtualsOfImpossibleTypeDiscovery(self): @@ -29,18 +30,19 @@ class TypeDiscoveryTest(unittest.TestCase): self.assertEqual(type(a), Derived) def testMultipleInheritance(self): - obj = OtherMultipleDerived.createObject("Base1"); + obj = OtherMultipleDerived.createObject("Base1") self.assertEqual(type(obj), Base1) # PYSIDE-868: In case of multiple inheritance, a factory # function will return the base class wrapper. - obj = OtherMultipleDerived.createObject("MDerived1"); + obj = OtherMultipleDerived.createObject("MDerived1") self.assertEqual(type(obj), Base1) - obj = OtherMultipleDerived.createObject("SonOfMDerived1"); + obj = OtherMultipleDerived.createObject("SonOfMDerived1") self.assertEqual(type(obj), Base1) - obj = OtherMultipleDerived.createObject("MDerived3"); + obj = OtherMultipleDerived.createObject("MDerived3") self.assertEqual(type(obj), Base1) - obj = OtherMultipleDerived.createObject("OtherMultipleDerived"); + obj = OtherMultipleDerived.createObject("OtherMultipleDerived") self.assertEqual(type(obj), Base1) + if __name__ == '__main__': unittest.main() diff --git a/sources/shiboken6/tests/otherbinding/usersprimitivefromothermodule_test.py b/sources/shiboken6/tests/otherbinding/usersprimitivefromothermodule_test.py index b464fa56..15a98832 100644 --- a/sources/shiboken6/tests/otherbinding/usersprimitivefromothermodule_test.py +++ b/sources/shiboken6/tests/otherbinding/usersprimitivefromothermodule_test.py @@ -14,6 +14,7 @@ from shiboken_paths import init_paths init_paths() from other import Number + class UserDefinedPrimitiveTypeFromRequiredModuleTest(unittest.TestCase): def testUsersPrimitiveFromRequiredModuleAsArgument(self): @@ -28,5 +29,6 @@ class UserDefinedPrimitiveTypeFromRequiredModuleTest(unittest.TestCase): cpx = number.toComplex() self.assertEqual(number.value(), int(cpx.real)) + if __name__ == '__main__': unittest.main() diff --git a/sources/shiboken6/tests/otherbinding/wrongctor_test.py b/sources/shiboken6/tests/otherbinding/wrongctor_test.py index 9aacfa9a..b9251b42 100644 --- a/sources/shiboken6/tests/otherbinding/wrongctor_test.py +++ b/sources/shiboken6/tests/otherbinding/wrongctor_test.py @@ -10,17 +10,19 @@ from pathlib import Path sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from shiboken_paths import init_paths init_paths() -from sample import * -from other import * +from sample import Abstract, ObjectType +from other import OtherDerived + class Foo(OtherDerived): def __init__(self): - Abstract.__init__(self, 2) # this should raise an exception + Abstract.__init__(self, 2) # this should raise an exception + class Foo2(ObjectType, OtherDerived): def __init__(self): ObjectType.__init__(self) - Abstract.__init__(self, 2) # this should raise an exception + Abstract.__init__(self, 2) # this should raise an exception class WrongCtorTest(unittest.TestCase): diff --git a/sources/shiboken6/tests/qtxmltosphinxtest/qtxmltosphinxtest.cpp b/sources/shiboken6/tests/qtxmltosphinxtest/qtxmltosphinxtest.cpp index c877c3ba..45cecd1a 100644 --- a/sources/shiboken6/tests/qtxmltosphinxtest/qtxmltosphinxtest.cpp +++ b/sources/shiboken6/tests/qtxmltosphinxtest/qtxmltosphinxtest.cpp @@ -464,7 +464,7 @@ void QtXmlToSphinxTest::testTableFormattingIoDevice() void QtXmlToSphinxTest::testSnippetExtraction_data() { QTest::addColumn("file"); - QTest::addColumn("id"); + QTest::addColumn("id"); QTest::addColumn("expected"); const char *fileCpp = R"(bla @@ -476,7 +476,7 @@ snip2_line2 // ![snip2] // ![snip3] )"; - const QString id = u"snip2"_s; + constexpr auto id = "snip2"_L1; const QString expected = uR"(snip2_line1 snip2_line2 )"_s; @@ -497,7 +497,7 @@ snip2_line2 void QtXmlToSphinxTest::testSnippetExtraction() { QFETCH(QByteArray, file); - QFETCH(QString, id); + QFETCH(QLatin1StringView, id); QFETCH(QString, expected); QBuffer buffer(&file); diff --git a/sources/shiboken6/tests/samplebinding/__del___test.py b/sources/shiboken6/tests/samplebinding/__del___test.py index 2d38b24d..45688661 100644 --- a/sources/shiboken6/tests/samplebinding/__del___test.py +++ b/sources/shiboken6/tests/samplebinding/__del___test.py @@ -16,11 +16,13 @@ import sample delCalled = False + class MyObject(sample.ObjectType): def __del__(self): global delCalled delCalled = True + class TestDel(unittest.TestCase): def testIt(self): a = MyObject() @@ -29,6 +31,6 @@ class TestDel(unittest.TestCase): gc.collect() self.assertTrue(delCalled) + if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/samplebinding/abstract_test.py b/sources/shiboken6/tests/samplebinding/abstract_test.py index 252541af..89e87be1 100644 --- a/sources/shiboken6/tests/samplebinding/abstract_test.py +++ b/sources/shiboken6/tests/samplebinding/abstract_test.py @@ -15,10 +15,12 @@ init_paths() from sample import Abstract + class Incomplete(Abstract): def __init__(self): Abstract.__init__(self) + class Concrete(Abstract): def __init__(self): Abstract.__init__(self) @@ -60,12 +62,7 @@ class AbstractTest(unittest.TestCase): # Python and calling it from C++ is undefined until it's decided how to # cast the Python data types to void pointers c = Concrete() - self.assertEqual(c.pureVirtualReturningVoidPtr(),42) - - def testReimplementedVirtualMethodCall(self): - '''Test if instanciation of an abstract class raises the correct exception.''' - i = Concrete() - self.assertRaises(NotImplementedError, i.callPureVirtual) + self.assertEqual(c.pureVirtualReturningVoidPtr(), 42) def testReimplementedVirtualMethodCall(self): '''Test if a Python override of a virtual method is correctly called from C++.''' @@ -85,6 +82,6 @@ class AbstractTest(unittest.TestCase): c.callVirtualGettingEnum(Abstract.Short) self.assertTrue(c.virtual_getting_enum) + if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/samplebinding/addedfunction_test.py b/sources/shiboken6/tests/samplebinding/addedfunction_test.py index 879bba7f..0b568014 100644 --- a/sources/shiboken6/tests/samplebinding/addedfunction_test.py +++ b/sources/shiboken6/tests/samplebinding/addedfunction_test.py @@ -14,6 +14,7 @@ from shiboken_paths import init_paths init_paths() from sample import SampleNamespace, ObjectType, Point + class TestAddedFunctionsWithSimilarTypes(unittest.TestCase): '''Adds new signatures very similar to already existing ones.''' @@ -38,5 +39,6 @@ class TestAddedFunctionsWithSimilarTypes(unittest.TestCase): control = len(obj.objectName()) self.assertEqual(SampleNamespace.passReferenceToObjectType(obj), control) + if __name__ == '__main__': unittest.main() diff --git a/sources/shiboken6/tests/samplebinding/addedfunction_with_container_args_test.py b/sources/shiboken6/tests/samplebinding/addedfunction_with_container_args_test.py index 3a570ccf..2a739033 100644 --- a/sources/shiboken6/tests/samplebinding/addedfunction_with_container_args_test.py +++ b/sources/shiboken6/tests/samplebinding/addedfunction_with_container_args_test.py @@ -14,18 +14,20 @@ from shiboken_paths import init_paths init_paths() from sample import sum2d, sumproduct + class TestAddedFunctionsWithContainerArgs(unittest.TestCase): '''Tests added functions with nested and multi-argument container types.''' def testNestedContainerType(self): '''Test added function with single-argument containers.''' - values = [[1,2],[3,4,5],[6]] + values = [[1, 2], [3, 4, 5], [6]] self.assertEqual(sum2d(values), 21) def testMultiArgContainerType(self): '''Test added function with a two-argument container.''' - values = [(1,2),(3,4),(5,6)] + values = [(1, 2), (3, 4), (5, 6)] self.assertEqual(sumproduct(values), 44) + if __name__ == '__main__': unittest.main() diff --git a/sources/shiboken6/tests/samplebinding/argumentmodifications_test.py b/sources/shiboken6/tests/samplebinding/argumentmodifications_test.py index 8158f89d..b0ca56a6 100644 --- a/sources/shiboken6/tests/samplebinding/argumentmodifications_test.py +++ b/sources/shiboken6/tests/samplebinding/argumentmodifications_test.py @@ -16,6 +16,7 @@ init_paths() from sample import Modifications, Point + class ArgumentModificationsTest(unittest.TestCase): '''Test cases for method arguments modifications performed as described on typesystem.''' @@ -40,7 +41,8 @@ class ArgumentModificationsTest(unittest.TestCase): def testArgRemoval1(self): '''Tests argument removal modifications on Modifications.argRemoval1.''' - # void [-> PyObject*] argRemoval1(int, bool, Point = Point(1, 2) [removed], Point = Point(3, 4) [removed], int = 333) + # void [-> PyObject*] argRemoval1(int, bool, Point = Point(1, 2) [removed], + # Point = Point(3, 4) [removed], int = 333) # code-injection: returns tuple with received parameters plus removed ones a0, a1, a2 = 1, True, 2 self.assertEqual(self.mods.argRemoval1(a0, a1), (a0, a1, Point(1, 2), Point(3, 4), 333)) @@ -51,7 +53,8 @@ class ArgumentModificationsTest(unittest.TestCase): def testArgRemoval2(self): '''Tests argument removal modifications on Modifications.argRemoval2.''' - # void [-> PyObject*] argRemoval2(int, bool, Point = Point(1, 2) [removed], Point = Point(3, 4) [removed], int = 333) + # void [-> PyObject*] argRemoval2(int, bool, Point = Point(1, 2) + # [removed], Point = Point(3, 4) [removed], int = 333) # code-injection: returns tuple with received parameters plus removed ones a0, a1, a2 = 1, True, 2 self.assertEqual(self.mods.argRemoval2(a0, a1), (a0, a1, Point(1, 2), Point(3, 4), 333)) @@ -59,7 +62,8 @@ class ArgumentModificationsTest(unittest.TestCase): def testArgRemoval3(self): '''Tests argument removal modifications on Modifications.argRemoval3.''' - # void [-> PyObject*] argRemoval3(int, Point = Point(1, 2) [removed], bool = true, Point = Point(3, 4) [removed], int = 333) + # void [-> PyObject*] argRemoval3(int, Point = Point(1, 2) [removed], + # bool = true, Point = Point(3, 4) [removed], int = 333) # code-injection: returns tuple with received parameters plus removed ones a0, a1, a2 = 1, True, 2 self.assertEqual(self.mods.argRemoval3(a0), (a0, Point(1, 2), True, Point(3, 4), 333)) @@ -68,7 +72,8 @@ class ArgumentModificationsTest(unittest.TestCase): def testArgRemoval4(self): '''Tests argument removal modifications on Modifications.argRemoval4.''' - # void [-> PyObject*] argRemoval4(int, Point [removed, new val = Point(6, 9)], bool, Point = Point(3, 4) [removed], int = 333) + # void [-> PyObject*] argRemoval4(int, Point [removed, new val = Point(6, 9)], bool, + # Point = Point(3, 4) [removed], int = 333) # code-injection: returns tuple with received parameters plus removed ones a0, a1, a2 = 1, True, 2 self.assertRaises(TypeError, self.mods.argRemoval4, a0) @@ -88,6 +93,6 @@ class ArgumentModificationsTest(unittest.TestCase): # code-injection: returns tuple with received parameters plus removed ones self.assertEqual(self.mods.argRemoval5(a0, a1, a2), (200, a0, a1, a2)) + if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/samplebinding/array_numpy_test.py b/sources/shiboken6/tests/samplebinding/array_numpy_test.py index ba919412..0d73bca1 100644 --- a/sources/shiboken6/tests/samplebinding/array_numpy_test.py +++ b/sources/shiboken6/tests/samplebinding/array_numpy_test.py @@ -22,24 +22,26 @@ try: except ImportError: pass + class ArrayTester(unittest.TestCase): '''Test case for NumPy arrays.''' def testIntArray(self): - intList = numpy.array([1, 2, 3, 4], dtype = 'int32') + intList = numpy.array([1, 2, 3, 4], dtype='int32') self.assertEqual(sample.sumIntArray(intList), 10) def testDoubleArray(self): - doubleList = numpy.array([1, 2, 3, 4], dtype = 'double') + doubleList = numpy.array([1, 2, 3, 4], dtype='double') self.assertEqual(sample.sumDoubleArray(doubleList), 10) def testIntMatrix(self): - intMatrix = numpy.array([[1, 2, 3], [4, 5, 6]], dtype = 'int32') + intMatrix = numpy.array([[1, 2, 3], [4, 5, 6]], dtype='int32') self.assertEqual(sample.sumIntMatrix(intMatrix), 21) def testDoubleMatrix(self): - doubleMatrix = numpy.array([[1, 2, 3], [4, 5, 6]], dtype = 'double') + doubleMatrix = numpy.array([[1, 2, 3], [4, 5, 6]], dtype='double') self.assertEqual(sample.sumDoubleMatrix(doubleMatrix), 21) + if __name__ == '__main__' and hasNumPy: unittest.main() diff --git a/sources/shiboken6/tests/samplebinding/array_sequence_test.py b/sources/shiboken6/tests/samplebinding/array_sequence_test.py index cb5cbc49..ad65d58d 100644 --- a/sources/shiboken6/tests/samplebinding/array_sequence_test.py +++ b/sources/shiboken6/tests/samplebinding/array_sequence_test.py @@ -14,6 +14,7 @@ from shiboken_paths import init_paths init_paths() import sample + class ArrayTester(unittest.TestCase): '''Test case for arrays.''' @@ -30,5 +31,6 @@ class ArrayTester(unittest.TestCase): doubleList = [1.2, 2.3, 3.4, 4.5] self.assertEqual(sample.sumDoubleArray(doubleList), 11.4) + if __name__ == '__main__': unittest.main() diff --git a/sources/shiboken6/tests/samplebinding/bug_554_test.py b/sources/shiboken6/tests/samplebinding/bug_554_test.py index 6c4df639..a7e7a721 100644 --- a/sources/shiboken6/tests/samplebinding/bug_554_test.py +++ b/sources/shiboken6/tests/samplebinding/bug_554_test.py @@ -11,15 +11,15 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from shiboken_paths import init_paths init_paths() -from sample import * +from sample import ObjectType + class Bug554: def crash(self): class Crasher(ObjectType): pass + if __name__ == '__main__': bug = Bug554() bug.crash() - - diff --git a/sources/shiboken6/tests/samplebinding/bug_704_test.py b/sources/shiboken6/tests/samplebinding/bug_704_test.py index 6de8fa3e..c470fe72 100644 --- a/sources/shiboken6/tests/samplebinding/bug_704_test.py +++ b/sources/shiboken6/tests/samplebinding/bug_704_test.py @@ -25,13 +25,12 @@ def defineNewStyle(): class ObjectTypeTest(unittest.TestCase): - '''Test cases to avoid declaring Shiboken classes with multiple inheritance from old style classes.''' + '''Test cases to avoid declaring Shiboken classes with multiple inheritance + from old style classes.''' def testObjectTypeNewStype(self): defineNewStyle() - if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/samplebinding/bytearray_test.py b/sources/shiboken6/tests/samplebinding/bytearray_test.py index e7c421b9..e51a899f 100644 --- a/sources/shiboken6/tests/samplebinding/bytearray_test.py +++ b/sources/shiboken6/tests/samplebinding/bytearray_test.py @@ -34,7 +34,7 @@ class ByteArrayConcatenationOperatorTest(unittest.TestCase): def testConcatPythonStringAndByteArray(self): # Test concatenation of a Python string with a ByteArray, in this order. - concat_python_string_add_qbytearray_worked = True + concat_python_string_add_qbytearray_worked = True # noqa: F841 ba = ByteArray('foo') result = 'bar\x00' + ba self.assertEqual(type(result), ByteArray) @@ -80,14 +80,14 @@ class ByteArrayOperatorAt(unittest.TestCase): # ByteArray[x] where x is a valid index (reverse order). string = 'abcdefgh' obj = ByteArray(string) - for i in range(len(string)-1, 0, -1): + for i in range(len(string) - 1, 0, -1): self.assertEqual(obj[i], bytes(string[i], "UTF8")) def testOutOfRange(self): # ByteArray[x] where x is out of index. string = '1234567' obj = ByteArray(string) - self.assertRaises(IndexError, lambda :obj[len(string)]) + self.assertRaises(IndexError, lambda: obj[len(string)]) def testNullStrings(self): ba = ByteArray('\x00') diff --git a/sources/shiboken6/tests/samplebinding/child_return_test.py b/sources/shiboken6/tests/samplebinding/child_return_test.py index 97525bcb..f0ac7062 100644 --- a/sources/shiboken6/tests/samplebinding/child_return_test.py +++ b/sources/shiboken6/tests/samplebinding/child_return_test.py @@ -14,7 +14,8 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from shiboken_paths import init_paths init_paths() -from sample import * +from sample import ObjectType + class ReturnOfChildTest(unittest.TestCase): '''The BlackBox class has cases of ownership transference between C++ and Python.''' @@ -37,6 +38,6 @@ class ReturnOfChildTest(unittest.TestCase): gc.collect() self.assertRaises(RuntimeError, child.objectName) + if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/samplebinding/class_fields_test.py b/sources/shiboken6/tests/samplebinding/class_fields_test.py index 9da25e13..1eeb3d44 100644 --- a/sources/shiboken6/tests/samplebinding/class_fields_test.py +++ b/sources/shiboken6/tests/samplebinding/class_fields_test.py @@ -15,6 +15,7 @@ init_paths() from sample import Derived, Point, ObjectType + class TestAccessingCppFields(unittest.TestCase): '''Simple test case for accessing the exposed C++ class fields.''' @@ -36,7 +37,7 @@ class TestAccessingCppFields(unittest.TestCase): self.assertEqual(d.primitiveField, int(value)) # attribution with invalid type - self.assertRaises(TypeError, lambda : setattr(d, 'primitiveField', None)) + self.assertRaises(TypeError, lambda: setattr(d, 'primitiveField', None)) def testAccessingRenamedFields(self): '''Reads and writes a renamed field.''' @@ -72,7 +73,7 @@ class TestAccessingCppFields(unittest.TestCase): self.assertNotEqual(d.userPrimitiveField, old_value) # attribution with invalid type - self.assertRaises(TypeError, lambda : setattr(d, 'userPrimitiveField', None)) + self.assertRaises(TypeError, lambda: setattr(d, 'userPrimitiveField', None)) def testAccessingValueTypeField(self): '''Reads and writes a value type (in this case a 'Point') field.''' @@ -80,7 +81,7 @@ class TestAccessingCppFields(unittest.TestCase): self.assertEqual(type(d.valueTypeField), Point) # attribution - old_value = d.valueTypeField + old_value = d.valueTypeField # noqa: F841 new_value = Point(-10, 537) d.valueTypeField = new_value self.assertEqual(d.valueTypeField, new_value) @@ -92,7 +93,7 @@ class TestAccessingCppFields(unittest.TestCase): self.assertEqual(d.valueTypeField.y(), 20) # attribution with invalid type - self.assertRaises(TypeError, lambda : setattr(d, 'valueTypeField', 123)) + self.assertRaises(TypeError, lambda: setattr(d, 'valueTypeField', 123)) def testAccessingObjectTypeField(self): '''Reads and writes a object type (in this case an 'ObjectType') field.''' @@ -111,7 +112,7 @@ class TestAccessingCppFields(unittest.TestCase): self.assertEqual(d.objectTypeField, value) # attribution with invalid type - self.assertRaises(TypeError, lambda : setattr(d, 'objectTypeField', 123)) + self.assertRaises(TypeError, lambda: setattr(d, 'objectTypeField', 123)) @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount") def testRefCountingAccessingObjectTypeField(self): @@ -155,7 +156,7 @@ class TestAccessingCppFields(unittest.TestCase): # attribution old_value = d.bitField new_value = 1 - d.bitField= new_value + d.bitField = new_value self.assertEqual(d.bitField, new_value) self.assertNotEqual(d.bitField, old_value) @@ -165,7 +166,7 @@ class TestAccessingCppFields(unittest.TestCase): self.assertEqual(d.bitField, int(value)) # attribution with invalid type - self.assertRaises(TypeError, lambda : setattr(d, 'bitField', None)) + self.assertRaises(TypeError, lambda: setattr(d, 'bitField', None)) if __name__ == '__main__': diff --git a/sources/shiboken6/tests/samplebinding/collector_test.py b/sources/shiboken6/tests/samplebinding/collector_test.py index 376f91f1..4caebc62 100644 --- a/sources/shiboken6/tests/samplebinding/collector_test.py +++ b/sources/shiboken6/tests/samplebinding/collector_test.py @@ -33,6 +33,7 @@ class CollectorTest(unittest.TestCase): self.assertEqual(collector.size(), 5) self.assertEqual(collector.items(), [2, 3, 5, 7, 11]) + class CollectorExternalOperator(unittest.TestCase): '''Test cases for external operators of Collector''' @@ -57,4 +58,3 @@ class CollectorObjectType(unittest.TestCase): if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/samplebinding/complex_test.py b/sources/shiboken6/tests/samplebinding/complex_test.py index 6f0b23b2..454aff10 100644 --- a/sources/shiboken6/tests/samplebinding/complex_test.py +++ b/sources/shiboken6/tests/samplebinding/complex_test.py @@ -16,6 +16,7 @@ init_paths() import sample from sample import Point + class ComplexTest(unittest.TestCase): '''Test case for conversions between C++ Complex class to Python complex class''' @@ -47,11 +48,14 @@ class ComplexTest(unittest.TestCase): def testUsingTuples(self): cpx1, cpx2 = (1.2, 3.4), (5.6, 7.8) - self.assertEqual(sample.sumComplexPair((cpx1, cpx2)), sample.sumComplexPair((complex(*cpx1), complex(*cpx2)))) + self.assertEqual(sample.sumComplexPair((cpx1, cpx2)), + sample.sumComplexPair((complex(*cpx1), complex(*cpx2)))) cpx1, cpx2 = (1, 3), (5, 7) - self.assertEqual(sample.sumComplexPair((cpx1, cpx2)), sample.sumComplexPair((complex(*cpx1), complex(*cpx2)))) + self.assertEqual(sample.sumComplexPair((cpx1, cpx2)), + sample.sumComplexPair((complex(*cpx1), complex(*cpx2)))) cpx1, cpx2 = (1.2, 3), (5.6, 7) - self.assertEqual(sample.sumComplexPair((cpx1, cpx2)), sample.sumComplexPair((complex(*cpx1), complex(*cpx2)))) + self.assertEqual(sample.sumComplexPair((cpx1, cpx2)), + sample.sumComplexPair((complex(*cpx1), complex(*cpx2)))) cpx1, cpx2 = (1, 2, 3), (4, 5, 7) self.assertRaises(TypeError, sample.sumComplexPair, (cpx1, cpx2)) cpx1, cpx2 = ('1', '2'), ('4', '5') @@ -60,4 +64,3 @@ class ComplexTest(unittest.TestCase): if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/samplebinding/conversion_operator_test.py b/sources/shiboken6/tests/samplebinding/conversion_operator_test.py index f349ae82..7e76245b 100644 --- a/sources/shiboken6/tests/samplebinding/conversion_operator_test.py +++ b/sources/shiboken6/tests/samplebinding/conversion_operator_test.py @@ -15,11 +15,13 @@ init_paths() from sample import Time, StrList + class ConversionOperatorTest(unittest.TestCase): '''Test cases for implicit conversion generated by conversion operator.''' def testConversionOperator(self): - '''Time defined an conversion operator for Str, so passing a Time object to a method expecting a Str should work.''' + '''Time defined an conversion operator for Str, so passing a Time object + to a method expecting a Str should work.''' t = Time(1, 2, 3) t_str = t.toString() sl = StrList() @@ -30,6 +32,6 @@ class ConversionOperatorTest(unittest.TestCase): self.assertEqual(len(sl), 1) self.assertEqual(sl[0], t_str) + if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/samplebinding/copy_test.py b/sources/shiboken6/tests/samplebinding/copy_test.py index 0a0533a4..db539d1b 100644 --- a/sources/shiboken6/tests/samplebinding/copy_test.py +++ b/sources/shiboken6/tests/samplebinding/copy_test.py @@ -63,4 +63,3 @@ class PicklingTest(unittest.TestCase): if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/samplebinding/ctorconvrule_test.py b/sources/shiboken6/tests/samplebinding/ctorconvrule_test.py index 0ad21e35..5e2695d7 100644 --- a/sources/shiboken6/tests/samplebinding/ctorconvrule_test.py +++ b/sources/shiboken6/tests/samplebinding/ctorconvrule_test.py @@ -15,6 +15,7 @@ init_paths() from sample import CtorConvRule + class TestCtorConvRule(unittest.TestCase): '''Simple test case for CtorConvRule''' @@ -24,6 +25,6 @@ class TestCtorConvRule(unittest.TestCase): obj = CtorConvRule(value) self.assertEqual(obj.value(), value + 1) + if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/samplebinding/cyclic_test.py b/sources/shiboken6/tests/samplebinding/cyclic_test.py index b72e41b1..4e4ae260 100644 --- a/sources/shiboken6/tests/samplebinding/cyclic_test.py +++ b/sources/shiboken6/tests/samplebinding/cyclic_test.py @@ -16,7 +16,6 @@ from sample import ObjectView from sample import ObjectModel - class ObjTest(unittest.TestCase): def test_cyclic_dependency_withParent(self): @@ -37,7 +36,7 @@ class ObjTest(unittest.TestCase): # turn off automatic garbage collection, to be able to trigger it # at the 'right' time gc.disable() - alive = lambda :sum(isinstance(o, CyclicObject) for o in gc.get_objects() ) + alive = lambda: sum(isinstance(o, CyclicObject) for o in gc.get_objects()) # noqa: E731 # # first proof that the wizard is only destructed by the garbage @@ -70,7 +69,7 @@ class ObjTest(unittest.TestCase): # turn off automatic garbage collection, to be able to trigger it # at the 'right' time gc.disable() - alive = lambda :sum(isinstance(o, CyclicObject) for o in gc.get_objects() ) + alive = lambda: sum(isinstance(o, CyclicObject) for o in gc.get_objects()) # noqa: E731 # # first proof that the wizard is only destructed by the garbage @@ -85,6 +84,6 @@ class ObjTest(unittest.TestCase): gc.collect() self.assertFalse(alive()) + if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/samplebinding/date_test.py b/sources/shiboken6/tests/samplebinding/date_test.py index a35861c8..2b6efcf1 100644 --- a/sources/shiboken6/tests/samplebinding/date_test.py +++ b/sources/shiboken6/tests/samplebinding/date_test.py @@ -16,6 +16,7 @@ from datetime import date from sample import SbkDate + class DateConversionTest(unittest.TestCase): def testConstructorWithDateObject(self): @@ -32,6 +33,6 @@ class DateConversionTest(unittest.TestCase): self.assertTrue(cDate.month(), pyDate.month) self.assertTrue(cDate.year(), pyDate.year) + if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/samplebinding/decisor_test.py b/sources/shiboken6/tests/samplebinding/decisor_test.py index 6c7b4482..0d39c5f9 100644 --- a/sources/shiboken6/tests/samplebinding/decisor_test.py +++ b/sources/shiboken6/tests/samplebinding/decisor_test.py @@ -15,6 +15,7 @@ init_paths() from sample import SampleNamespace, Point, ObjectType, ObjectModel + class DecisorTest(unittest.TestCase): '''Test cases for the method overload decisor.''' @@ -37,11 +38,15 @@ class DecisorTest(unittest.TestCase): '''Call methods overloads that receive parent and inheritor classes' instances.''' objecttype = ObjectType() objectmodel = ObjectModel() - self.assertEqual(ObjectModel.receivesObjectTypeFamily(objecttype), ObjectModel.ObjectTypeCalled) - self.assertNotEqual(ObjectModel.receivesObjectTypeFamily(objecttype), ObjectModel.ObjectModelCalled) - self.assertEqual(ObjectModel.receivesObjectTypeFamily(objectmodel), ObjectModel.ObjectModelCalled) - self.assertNotEqual(ObjectModel.receivesObjectTypeFamily(objectmodel), ObjectModel.ObjectTypeCalled) + self.assertEqual(ObjectModel.receivesObjectTypeFamily(objecttype), + ObjectModel.ObjectTypeCalled) + self.assertNotEqual(ObjectModel.receivesObjectTypeFamily(objecttype), + ObjectModel.ObjectModelCalled) + self.assertEqual(ObjectModel.receivesObjectTypeFamily(objectmodel), + ObjectModel.ObjectModelCalled) + self.assertNotEqual(ObjectModel.receivesObjectTypeFamily(objectmodel), + ObjectModel.ObjectTypeCalled) + if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/samplebinding/delete_test.py b/sources/shiboken6/tests/samplebinding/delete_test.py index 5a4ee090..57a792ae 100644 --- a/sources/shiboken6/tests/samplebinding/delete_test.py +++ b/sources/shiboken6/tests/samplebinding/delete_test.py @@ -14,15 +14,15 @@ init_paths() import sample from shiboken6 import Shiboken + class DeleteTest(unittest.TestCase): def testNonCppWrapperClassDelete(self): - """Would segfault when shiboken.delete called on obj not created from - Python """ + """Would segfault when shiboken.delete called on obj not created from Python.""" obj = sample.ObjectType() child = obj.createChild(None) Shiboken.delete(child) assert not Shiboken.isValid(child) + if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/samplebinding/deprecated_test.py b/sources/shiboken6/tests/samplebinding/deprecated_test.py index 95b07f83..c371df94 100644 --- a/sources/shiboken6/tests/samplebinding/deprecated_test.py +++ b/sources/shiboken6/tests/samplebinding/deprecated_test.py @@ -14,11 +14,13 @@ init_paths() from sample import ObjectType + class TestDeprecatedCall(unittest.TestCase): def testCallWithError(self): o = ObjectType() warnings.simplefilter('error') self.assertRaises(DeprecationWarning, o.deprecatedFunction) + if __name__ == '__main__': unittest.main() diff --git a/sources/shiboken6/tests/samplebinding/derived_test.py b/sources/shiboken6/tests/samplebinding/derived_test.py index a92ccd31..346f2913 100644 --- a/sources/shiboken6/tests/samplebinding/derived_test.py +++ b/sources/shiboken6/tests/samplebinding/derived_test.py @@ -16,6 +16,7 @@ init_paths() import sample from sample import Abstract, Derived, DerivedUsingCt, OverloadedFuncEnum + class Deviant(Derived): def __init__(self): Derived.__init__(self) @@ -31,6 +32,16 @@ class Deviant(Derived): def className(self): return 'Deviant' + +class ImplementVirtualWithOutParameter(Derived): + def __init__(self, value): + super().__init__() + self._value = value + + def virtualWithOutParameter(self): + return self._value + + class DerivedTest(unittest.TestCase): '''Test case for Derived class''' @@ -59,18 +70,21 @@ class DerivedTest(unittest.TestCase): self.assertEqual(type(result), OverloadedFuncEnum) def testOverloadedMethodCallWithWrongNumberOfArguments(self): - '''Test if a call to an overloaded method with the wrong number of arguments raises an exception.''' + '''Test if a call to an overloaded method with the wrong number of arguments + raises an exception.''' derived = Derived() self.assertRaises(TypeError, derived.otherOverloaded, 1, 2, True) def testReimplementedPureVirtualMethodCall(self): - '''Test if a Python override of a implemented pure virtual method is correctly called from C++.''' + '''Test if a Python override of a implemented pure virtual method is + correctly called from C++.''' d = Deviant() d.callPureVirtual() self.assertTrue(d.pure_virtual_called) def testReimplementedVirtualMethodCall(self): - '''Test if a Python override of a reimplemented virtual method is correctly called from C++.''' + '''Test if a Python override of a reimplemented virtual method is + correctly called from C++.''' d = Deviant() d.callUnpureVirtual() self.assertTrue(d.unpure_virtual_called) @@ -82,7 +96,8 @@ class DerivedTest(unittest.TestCase): self.assertEqual(d.getClassName(), 'Derived') def testReimplementedVirtualMethodCallReturningString(self): - '''Test if a Python override of a reimplemented virtual method is correctly called from C++.''' + '''Test if a Python override of a reimplemented virtual method is + correctly called from C++.''' d = Deviant() self.assertEqual(d.className(), 'Deviant') self.assertEqual(d.getClassName(), 'Deviant') @@ -106,7 +121,8 @@ class DerivedTest(unittest.TestCase): self.assertEqual(Abstract.getObjectId(d), objId) def testObjectCreationWithParentType(self): - '''Derived class creates an instance of itself in C++ and returns it as a pointer to its ancestor Abstract.''' + '''Derived class creates an instance of itself in C++ and returns it as + a pointer to its ancestor Abstract.''' obj = Derived.createObject() self.assertEqual(type(obj), Derived) @@ -115,7 +131,13 @@ class DerivedTest(unittest.TestCase): obj = DerivedUsingCt(42) self.assertEqual(obj.value(), 42) + def testVirtualWithOutParameter(self): + d = Derived() + self.assertEqual(d.callVirtualWithOutParameter(), 42) + + d = ImplementVirtualWithOutParameter(1) + self.assertEqual(d.callVirtualWithOutParameter(), 1) + if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/samplebinding/duck_punching_test.py b/sources/shiboken6/tests/samplebinding/duck_punching_test.py index e1d89e2c..aa21a0f7 100644 --- a/sources/shiboken6/tests/samplebinding/duck_punching_test.py +++ b/sources/shiboken6/tests/samplebinding/duck_punching_test.py @@ -16,6 +16,7 @@ init_paths() from sample import VirtualMethods, SimpleFile, Point + def MethodTypeCompat(func, instance): return types.MethodType(func, instance) @@ -24,10 +25,12 @@ class Duck(VirtualMethods): def __init__(self): VirtualMethods.__init__(self) + class Monkey(SimpleFile): def __init__(self, filename): SimpleFile.__init__(self, filename) + class DuckPunchingTest(unittest.TestCase): '''Test case for duck punching (aka "monkey patching").''' @@ -56,7 +59,8 @@ class DuckPunchingTest(unittest.TestCase): result2 = vm.virtualMethod0(pt, val, cpx, b) self.assertEqual(result1, result2) - self.assertEqual(result1, VirtualMethods.virtualMethod0(vm, pt, val, cpx, b) * self.multiplier) + self.assertEqual(result1, + VirtualMethods.virtualMethod0(vm, pt, val, cpx, b) * self.multiplier) # This is done to decrease the refcount of the vm object # allowing the object wrapper to be deleted before the @@ -66,7 +70,8 @@ class DuckPunchingTest(unittest.TestCase): vm.virtualMethod0 = None def testMonkeyPatchOnVirtualMethodWithInheritance(self): - '''Injects new 'virtualMethod0' on an object that inherits from VirtualMethods and makes C++ call it.''' + '''Injects new 'virtualMethod0' on an object that inherits from + VirtualMethods and makes C++ call it.''' duck = Duck() pt, val, cpx, b = Point(1.1, 2.2), 4, complex(3.3, 4.4), True @@ -85,7 +90,8 @@ class DuckPunchingTest(unittest.TestCase): result2 = duck.virtualMethod0(pt, val, cpx, b) self.assertEqual(result1, result2) - self.assertEqual(result1, VirtualMethods.virtualMethod0(duck, pt, val, cpx, b) * self.multiplier) + self.assertEqual(result1, + VirtualMethods.virtualMethod0(duck, pt, val, cpx, b) * self.multiplier) duck.virtualMethod0 = None @@ -150,4 +156,3 @@ class DuckPunchingTest(unittest.TestCase): if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/samplebinding/echo_test.py b/sources/shiboken6/tests/samplebinding/echo_test.py index 567d1452..f1859260 100644 --- a/sources/shiboken6/tests/samplebinding/echo_test.py +++ b/sources/shiboken6/tests/samplebinding/echo_test.py @@ -15,6 +15,7 @@ init_paths() from sample import Echo + class TestEcho(unittest.TestCase): '''Simple test case for Echo.echo''' @@ -26,7 +27,8 @@ class TestEcho(unittest.TestCase): def testCallOperator(self): e = Echo() - self.assertEqual(e("Hello", 3), "Hello3"); + self.assertEqual(e("Hello", 3), "Hello3") + + if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/samplebinding/enum_test.py b/sources/shiboken6/tests/samplebinding/enum_test.py index c140a12b..276b8d89 100644 --- a/sources/shiboken6/tests/samplebinding/enum_test.py +++ b/sources/shiboken6/tests/samplebinding/enum_test.py @@ -13,12 +13,10 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from shiboken_paths import init_paths init_paths() -import shiboken6 # This is needed after the introduction of BUILD_DIR. import sample from sample import SampleNamespace, ObjectType, Event -from shibokensupport.signature import get_signature def createTempFile(): @@ -69,8 +67,10 @@ class EnumTest(unittest.TestCase): def testEnumItemAsDefaultValueToIntArgument(self): '''Calls function with an enum item as default value to an int argument.''' - self.assertEqual(SampleNamespace.enumItemAsDefaultValueToIntArgument(), SampleNamespace.ZeroIn) - self.assertEqual(SampleNamespace.enumItemAsDefaultValueToIntArgument(SampleNamespace.ZeroOut), SampleNamespace.ZeroOut) + self.assertEqual(SampleNamespace.enumItemAsDefaultValueToIntArgument(), + SampleNamespace.ZeroIn) + self.assertEqual(SampleNamespace.enumItemAsDefaultValueToIntArgument(SampleNamespace.ZeroOut), # noqa E:501 + SampleNamespace.ZeroOut) self.assertEqual(SampleNamespace.enumItemAsDefaultValueToIntArgument(123), 123) def testAnonymousGlobalEnums(self): @@ -101,7 +101,8 @@ class EnumTest(unittest.TestCase): def testEnumArgumentWithDefaultValue(self): '''Option enumArgumentWithDefaultValue(Option opt = UnixTime);''' self.assertEqual(SampleNamespace.enumArgumentWithDefaultValue(), SampleNamespace.UnixTime) - self.assertEqual(SampleNamespace.enumArgumentWithDefaultValue(SampleNamespace.RandomNumber), SampleNamespace.RandomNumber) + self.assertEqual(SampleNamespace.enumArgumentWithDefaultValue(SampleNamespace.RandomNumber), # noqa E:501 + SampleNamespace.RandomNumber) class MyEvent(Event): @@ -135,4 +136,3 @@ class EnumOperators(unittest.TestCase): if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/samplebinding/enumfromremovednamespace_test.py b/sources/shiboken6/tests/samplebinding/enumfromremovednamespace_test.py index 004caac7..42ae2396 100644 --- a/sources/shiboken6/tests/samplebinding/enumfromremovednamespace_test.py +++ b/sources/shiboken6/tests/samplebinding/enumfromremovednamespace_test.py @@ -14,8 +14,6 @@ init_paths() import sample from shiboken_test_helper import objectFullname -from shiboken6 import Shiboken - from shibokensupport.signature import get_signature @@ -46,9 +44,8 @@ class TestEnumFromRemovedNamespace(unittest.TestCase): sample.UnremovedNamespace.RemovedNamespace3_AnonymousEnum_Value0 def testNestedFunctionFromRemovedNamespace(self): - self.assertEqual(sample.UnremovedNamespace.nestedMathSum(1, 2), 3) + self.assertEqual(sample.UnremovedNamespace.nestedMathSum(1, 2), 3) if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/samplebinding/event_loop_call_virtual_test.py b/sources/shiboken6/tests/samplebinding/event_loop_call_virtual_test.py index e6165640..8e13d5d4 100644 --- a/sources/shiboken6/tests/samplebinding/event_loop_call_virtual_test.py +++ b/sources/shiboken6/tests/samplebinding/event_loop_call_virtual_test.py @@ -6,14 +6,12 @@ import os import sys -import time import unittest from pathlib import Path sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from shiboken_paths import init_paths init_paths() -from random import random from sample import ObjectType, Event @@ -41,7 +39,7 @@ class TestEventLoop(unittest.TestCase): objs = [ObjectType(), NoOverride(), Override()] evaluated = ObjectType.processEvent(objs, - Event(Event.BASIC_EVENT)) + Event(Event.BASIC_EVENT)) self.assertEqual(evaluated, 3) self.assertTrue(objs[2].called) diff --git a/sources/shiboken6/tests/samplebinding/event_loop_thread_test.py b/sources/shiboken6/tests/samplebinding/event_loop_thread_test.py index 2cd1c8d0..8b854fca 100644 --- a/sources/shiboken6/tests/samplebinding/event_loop_thread_test.py +++ b/sources/shiboken6/tests/samplebinding/event_loop_thread_test.py @@ -64,7 +64,7 @@ class TestEventLoopWithThread(unittest.TestCase): thread.start() evaluated = ObjectType.processEvent(objs, - Event(Event.BASIC_EVENT)) + Event(Event.BASIC_EVENT)) thread.join() diff --git a/sources/shiboken6/tests/samplebinding/exception_test.py b/sources/shiboken6/tests/samplebinding/exception_test.py index 78e583da..d9e6b377 100644 --- a/sources/shiboken6/tests/samplebinding/exception_test.py +++ b/sources/shiboken6/tests/samplebinding/exception_test.py @@ -13,6 +13,7 @@ init_paths() from sample import ExceptionTest + class CppExceptionTest(unittest.TestCase): def testVoid(self): @@ -23,14 +24,14 @@ class CppExceptionTest(unittest.TestCase): try: et.voidThrowStdException(True) - except: + except: # noqa: E722 exceptionCount += 1 et.voidThrowInt(False) try: et.voidThrowInt(True) - except: + except: # noqa: E722 exceptionCount += 1 self.assertEqual(exceptionCount, 2) @@ -39,18 +40,18 @@ class CppExceptionTest(unittest.TestCase): exceptionCount = 0 et = ExceptionTest() - result = et.intThrowStdException(False); + result = et.intThrowStdException(False) try: - result = et.intThrowStdException(True); - except: + result = et.intThrowStdException(True) + except: # noqa: E722 exceptionCount += 1 - result = et.intThrowInt(False); + result = et.intThrowInt(False) try: - result = et.intThrowInt(True); - except: + result = et.intThrowInt(True) # noqa: F841 + except: # noqa: E722 exceptionCount += 1 self.assertEqual(exceptionCount, 2) @@ -60,8 +61,8 @@ class CppExceptionTest(unittest.TestCase): when return ownership modifications are generated.""" exceptionCount = 0 try: - et = ExceptionTest.create(True); - except: + et = ExceptionTest.create(True) # noqa: F841 + except: # noqa: E722 exceptionCount += 1 self.assertEqual(exceptionCount, 1) diff --git a/sources/shiboken6/tests/samplebinding/filter_test.py b/sources/shiboken6/tests/samplebinding/filter_test.py index 86c5fba2..df805093 100644 --- a/sources/shiboken6/tests/samplebinding/filter_test.py +++ b/sources/shiboken6/tests/samplebinding/filter_test.py @@ -12,6 +12,7 @@ init_paths() from sample import Data, Intersection, Union + class TestFilters(unittest.TestCase): def testAnd(self): @@ -23,5 +24,6 @@ class TestFilters(unittest.TestCase): self.assertEqual(type(inter), Intersection) + if __name__ == '__main__': unittest.main() diff --git a/sources/shiboken6/tests/samplebinding/handleholder_test.py b/sources/shiboken6/tests/samplebinding/handleholder_test.py index 27391571..af22328c 100644 --- a/sources/shiboken6/tests/samplebinding/handleholder_test.py +++ b/sources/shiboken6/tests/samplebinding/handleholder_test.py @@ -17,6 +17,7 @@ init_paths() from sample import HandleHolder + class HandleHolderTest(unittest.TestCase): def testCreation(self): holder = HandleHolder(HandleHolder.createHandle()) @@ -33,5 +34,6 @@ class HandleHolderTest(unittest.TestCase): holder2 = HandleHolder(holder.handle2()) self.assertTrue(holder.compare2(holder2)) + if __name__ == '__main__': unittest.main() diff --git a/sources/shiboken6/tests/samplebinding/hashabletype_test.py b/sources/shiboken6/tests/samplebinding/hashabletype_test.py index 20d3d2ed..c41f5cc0 100644 --- a/sources/shiboken6/tests/samplebinding/hashabletype_test.py +++ b/sources/shiboken6/tests/samplebinding/hashabletype_test.py @@ -13,7 +13,8 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from shiboken_paths import init_paths init_paths() -from sample import * +from sample import ObjectType, Str + class HashableTest(unittest.TestCase): @@ -29,6 +30,6 @@ class HashableTest(unittest.TestCase): h[o] = 2 self.assertTrue(h.get(o), 2) + if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/samplebinding/ignorederefop_test.py b/sources/shiboken6/tests/samplebinding/ignorederefop_test.py index 89df035a..feb78d04 100644 --- a/sources/shiboken6/tests/samplebinding/ignorederefop_test.py +++ b/sources/shiboken6/tests/samplebinding/ignorederefop_test.py @@ -9,12 +9,14 @@ from pathlib import Path sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from shiboken_paths import init_paths init_paths() -from sample import * +from sample import Reference + class TestLackOfDereferenceOperators (unittest.TestCase): def testIf(self): r = Reference() self.assertFalse(hasattr(r, "__mul__")) + if __name__ == '__main__': unittest.main() diff --git a/sources/shiboken6/tests/samplebinding/implicitconv_numerical_test.py b/sources/shiboken6/tests/samplebinding/implicitconv_numerical_test.py index c5c7bc0d..08166628 100644 --- a/sources/shiboken6/tests/samplebinding/implicitconv_numerical_test.py +++ b/sources/shiboken6/tests/samplebinding/implicitconv_numerical_test.py @@ -27,6 +27,7 @@ if is64bitArchitecture and sys.platform != 'win32': cLongMin = -9223372036854775808 cLongMax = 9223372036854775807 + class NumericTester(unittest.TestCase): '''Helper class for numeric comparison testing''' diff --git a/sources/shiboken6/tests/samplebinding/implicitconv_test.py b/sources/shiboken6/tests/samplebinding/implicitconv_test.py index 96cad9c6..ebafe0c5 100644 --- a/sources/shiboken6/tests/samplebinding/implicitconv_test.py +++ b/sources/shiboken6/tests/samplebinding/implicitconv_test.py @@ -15,6 +15,7 @@ init_paths() from sample import ImplicitConv, ObjectType + class ImplicitConvTest(unittest.TestCase): '''Test case for implicit conversions''' @@ -43,4 +44,3 @@ class ImplicitConvTest(unittest.TestCase): if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/samplebinding/inheritanceandscope_test.py b/sources/shiboken6/tests/samplebinding/inheritanceandscope_test.py index 7ae67610..28d62486 100644 --- a/sources/shiboken6/tests/samplebinding/inheritanceandscope_test.py +++ b/sources/shiboken6/tests/samplebinding/inheritanceandscope_test.py @@ -15,14 +15,16 @@ init_paths() from sample import SampleNamespace + class ScopeAndInheritanceTest(unittest.TestCase): '''Test cases for finding scope in cases involving inheritance.''' def testMethodCorrectlyWrapper(self): '''A method returning a type declared in the scope of the method's class parent must be found and the method correctly exported.''' - meth = getattr(SampleNamespace.DerivedFromNamespace, 'methodReturningTypeFromParentScope') + meth = getattr(SampleNamespace.DerivedFromNamespace, # noqa: F841 + 'methodReturningTypeFromParentScope') + if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/samplebinding/injectcode_test.py b/sources/shiboken6/tests/samplebinding/injectcode_test.py index 90225a03..f673a780 100644 --- a/sources/shiboken6/tests/samplebinding/injectcode_test.py +++ b/sources/shiboken6/tests/samplebinding/injectcode_test.py @@ -14,6 +14,7 @@ from shiboken_paths import init_paths init_paths() from sample import InjectCode + class MyInjectCode(InjectCode): def __init__(self): InjectCode.__init__(self) @@ -22,10 +23,11 @@ class MyInjectCode(InjectCode): def arrayMethod(self, values): return self.multiplier * sum(values) + class InjectCodeTest(unittest.TestCase): @unittest.skipIf(hasattr(sys, "pypy_version_info"), - "PyPy type objects cannot be modified (yet) after creation") + "PyPy type objects cannot be modified (yet) after creation") def testTypeNativeBeginning_TypeTargetBeginning(self): ic = InjectCode() self.assertEqual(str(ic), "Hi! I'm the inject code dummy class.") @@ -71,22 +73,24 @@ class InjectCodeTest(unittest.TestCase): self.assertEqual(result, sum(values)) def testCallReimplementedVirtualMethodWithArgumentRemovalAndArgumentTypeModification(self): - '''Calls a reimplemented virtual method that had its first argument removed and the second modified.''' + '''Calls a reimplemented virtual method that had its first argument removed + and the second modified.''' ic = MyInjectCode() values = (1, 2, 3, 4, 5) result = ic.callArrayMethod(values) self.assertEqual(result, ic.multiplier * sum(values)) def testUsageOfTypeSystemCheckVariableOnPrimitiveType(self): - '''When the sequence item is convertible to an integer -1 is returned, or -2 if its not convertible.''' + '''When the sequence item is convertible to an integer -1 is returned, + or -2 if its not convertible.''' ic = InjectCode() values = (1, 2, 3, 4, '5', 6.7) result = ic.arrayMethod(values) - fixedValues = [v for v in values if isinstance(v, int)]\ - + [-1 for v in values if isinstance(v, float)]\ - + [-2 for v in values if not isinstance(v, int) and not isinstance(v, float)] - self.assertEqual(result, sum(fixedValues)) + ints = [v for v in values if isinstance(v, int)] + floats = [-1 for v in values if isinstance(v, float)] + other = [-2 for v in values if not isinstance(v, int) and not isinstance(v, float)] + self.assertEqual(result, sum(ints + floats + other)) class IntArrayTest(unittest.TestCase): @@ -110,5 +114,6 @@ class IntArrayTest(unittest.TestCase): ic = InjectCode() self.assertEqual(sum([1, 2]) + len([1, 2]), ic.sumArrayAndLength(args)) + if __name__ == '__main__': unittest.main() diff --git a/sources/shiboken6/tests/samplebinding/innerclass_test.py b/sources/shiboken6/tests/samplebinding/innerclass_test.py index f94c5775..721f3348 100644 --- a/sources/shiboken6/tests/samplebinding/innerclass_test.py +++ b/sources/shiboken6/tests/samplebinding/innerclass_test.py @@ -13,10 +13,11 @@ init_paths() from sample import Derived + class TestInnerClass(unittest.TestCase): def testInstaciate(self): - d = Derived.SomeInnerClass() + d = Derived.SomeInnerClass() # noqa: F841 + if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/samplebinding/intlist_test.py b/sources/shiboken6/tests/samplebinding/intlist_test.py index 41d2b659..defa9ca7 100644 --- a/sources/shiboken6/tests/samplebinding/intlist_test.py +++ b/sources/shiboken6/tests/samplebinding/intlist_test.py @@ -13,6 +13,7 @@ init_paths() from sample import IntList + class IntListTest(unittest.TestCase): def testAutoFunctionsToBaseList(self): @@ -74,5 +75,6 @@ class IntListTest(unittest.TestCase): self.assertEqual(il[1], int(432.1)) self.assertRaises(TypeError, il.__setitem__, 2, '78') + if __name__ == '__main__': unittest.main() diff --git a/sources/shiboken6/tests/samplebinding/intwrapper_test.py b/sources/shiboken6/tests/samplebinding/intwrapper_test.py index 21cab6b3..d883adf4 100644 --- a/sources/shiboken6/tests/samplebinding/intwrapper_test.py +++ b/sources/shiboken6/tests/samplebinding/intwrapper_test.py @@ -16,8 +16,8 @@ from sample import IntWrapper class IntWrapperTest(unittest.TestCase): def testOperators(self): - ten1 = IntWrapper(10) - ten2 = IntWrapper(10) + ten1 = IntWrapper(10) + ten2 = IntWrapper(10) twenty = IntWrapper(20) self.assertTrue(ten1 == ten2) self.assertTrue(ten1 != twenty) diff --git a/sources/shiboken6/tests/samplebinding/invalid_virtual_return_test.py b/sources/shiboken6/tests/samplebinding/invalid_virtual_return_test.py index e3d06043..bb35b2bb 100644 --- a/sources/shiboken6/tests/samplebinding/invalid_virtual_return_test.py +++ b/sources/shiboken6/tests/samplebinding/invalid_virtual_return_test.py @@ -38,7 +38,7 @@ class ModelWrongReturnTest(unittest.TestCase): def testWrongTypeReturn(self): model = ListModelWrong() view = ObjectView(model) - self.assertRaises(RuntimeWarning, view.getRawModelData) # calls model.data() + self.assertRaises(RuntimeWarning, view.getRawModelData) # calls model.data() if __name__ == '__main__': diff --git a/sources/shiboken6/tests/samplebinding/keep_reference_test.py b/sources/shiboken6/tests/samplebinding/keep_reference_test.py index 6cf416fc..10591fec 100644 --- a/sources/shiboken6/tests/samplebinding/keep_reference_test.py +++ b/sources/shiboken6/tests/samplebinding/keep_reference_test.py @@ -2,8 +2,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -'''Test case for objects that keep references to other object without owning them (e.g. model/view relationships).''' - import os import sys import unittest @@ -15,8 +13,10 @@ init_paths() from sample import ObjectModel, ObjectView + class TestKeepReference(unittest.TestCase): - '''Test case for objects that keep references to other object without owning them (e.g. model/view relationships).''' + '''Test case for objects that keep references to other object without + owning them (e.g. model/view relationships).''' @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount") def testReferenceCounting(self): @@ -48,15 +48,16 @@ class TestKeepReference(unittest.TestCase): self.assertEqual(sys.getrefcount(model), refcount1) def testReferreedObjectSurvivalAfterContextEnd(self): - '''Model-like object assigned to a view-like object must survive after get out of context.''' + '''Model-like object assigned to a view-like object must survive + after get out of context.''' def createModelAndSetToView(view): model = ObjectModel() model.setObjectName('created model') view.setModel(model) view = ObjectView() createModelAndSetToView(view) - model = view.model() + model = view.model() # noqa: F841 + if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/samplebinding/list_test.py b/sources/shiboken6/tests/samplebinding/list_test.py index 6eb975b9..b668bfd9 100644 --- a/sources/shiboken6/tests/samplebinding/list_test.py +++ b/sources/shiboken6/tests/samplebinding/list_test.py @@ -15,6 +15,7 @@ init_paths() from sample import ListUser, Point, PointF + class ExtendedListUser(ListUser): def __init__(self): ListUser.__init__(self) @@ -24,6 +25,7 @@ class ExtendedListUser(ListUser): self.create_list_called = True return [2, 3, 5, 7, 13] + class ListConversionTest(unittest.TestCase): '''Test case for std::list container conversions''' @@ -69,7 +71,8 @@ class ListConversionTest(unittest.TestCase): self.assertEqual(result, lst) def testConversionInBothDirectionsWithSimilarContainer(self): - '''Test converting a tuple, instead of the expected list, from Python to C++ and back again.''' + '''Test converting a tuple, instead of the expected list, + from Python to C++ and back again.''' lu = ListUser() lst = (3, 5, 7) lu.setList(lst) @@ -96,6 +99,6 @@ class ListConversionTest(unittest.TestCase): self.assertEqual(ListUser.ListOfPointF, ListUser.listOfPoints([PointF()])) self.assertEqual(ListUser.ListOfPoint, ListUser.listOfPoints([Point()])) + if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/samplebinding/lock_test.py b/sources/shiboken6/tests/samplebinding/lock_test.py index 0657d41b..acd47634 100644 --- a/sources/shiboken6/tests/samplebinding/lock_test.py +++ b/sources/shiboken6/tests/samplebinding/lock_test.py @@ -63,7 +63,8 @@ class TestLockUnlock(unittest.TestCase): self.assertTrue(result) def testReimplementedVirtualBlocker(self): - '''Same as the basic case but blocker method is a C++ virtual reimplemented in Python and called from C++.''' + '''Same as the basic case but blocker method is a C++ virtual reimplemented + in Python and called from C++.''' mybucket = MyBucket() unlocker = Unlocker(mybucket) @@ -72,5 +73,6 @@ class TestLockUnlock(unittest.TestCase): unlocker.join() self.assertTrue(result) + if __name__ == '__main__': unittest.main() diff --git a/sources/shiboken6/tests/samplebinding/map_test.py b/sources/shiboken6/tests/samplebinding/map_test.py index 5ffed5ed..fa99ad2e 100644 --- a/sources/shiboken6/tests/samplebinding/map_test.py +++ b/sources/shiboken6/tests/samplebinding/map_test.py @@ -15,6 +15,7 @@ init_paths() from sample import MapUser + class ExtendedMapUser(MapUser): def __init__(self): MapUser.__init__(self) @@ -22,10 +23,11 @@ class ExtendedMapUser(MapUser): def createMap(self): self.create_map_called = True - return {'two' : (complex(2.2, 2.2), 2), - 'three' : (complex(3.3, 3.3), 3), - 'five' : (complex(5.5, 5.5), 5), - 'seven' : (complex(7.7, 7.7), 7)} + return {'two': (complex(2.2, 2.2), 2), + 'three': (complex(3.3, 3.3), 3), + 'five': (complex(5.5, 5.5), 5), + 'seven': (complex(7.7, 7.7), 7)} + class MapConversionTest(unittest.TestCase): '''Test case for std::map container conversions''' @@ -44,7 +46,7 @@ class MapConversionTest(unittest.TestCase): def testConversionInBothDirections(self): '''Test converting a map from Python to C++ and back again.''' mu = MapUser() - map_ = {'odds' : [2, 4, 6], 'evens' : [3, 5, 7], 'primes' : [3, 4, 6]} + map_ = {'odds': [2, 4, 6], 'evens': [3, 5, 7], 'primes': [3, 4, 6]} mu.setMap(map_) result = mu.getMap() self.assertEqual(result, map_) @@ -52,9 +54,10 @@ class MapConversionTest(unittest.TestCase): def testConversionMapIntKeyValueTypeValue(self): '''C++ signature: MapUser::passMapIntValueType(const std::map&)''' mu = MapUser() - map_ = {0 : 'string'} + map_ = {0: 'string'} result = mu.passMapIntValueType(map_) self.assertEqual(map_, result) + if __name__ == '__main__': unittest.main() diff --git a/sources/shiboken6/tests/samplebinding/metaclass_test.py b/sources/shiboken6/tests/samplebinding/metaclass_test.py index 787bace7..4d7eeda9 100644 --- a/sources/shiboken6/tests/samplebinding/metaclass_test.py +++ b/sources/shiboken6/tests/samplebinding/metaclass_test.py @@ -10,32 +10,40 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from shiboken_paths import init_paths init_paths() -from sample import * +from sample import Point + class MetaA(type): pass + class A(object): __metaclass__ = MetaA + MetaB = type(Point) B = Point + class MetaC(MetaA, MetaB): pass + + class C(A, B): __metaclass__ = MetaC + class D(C): pass + class TestMetaClass(unittest.TestCase): def testIt(self): - w1 = C() # works + w1 = C() # works w1.setX(1) w1.setY(2) - w2 = D() # should work! + w2 = D() # should work! w2.setX(3) w2.setY(4) diff --git a/sources/shiboken6/tests/samplebinding/mixed_mi_test.py b/sources/shiboken6/tests/samplebinding/mixed_mi_test.py index 776605a4..fa848160 100644 --- a/sources/shiboken6/tests/samplebinding/mixed_mi_test.py +++ b/sources/shiboken6/tests/samplebinding/mixed_mi_test.py @@ -52,5 +52,3 @@ class MixedInheritanceTest(unittest.TestCase): if __name__ == '__main__': unittest.main() - - diff --git a/sources/shiboken6/tests/samplebinding/modelindex_test.py b/sources/shiboken6/tests/samplebinding/modelindex_test.py index 725105a9..e23503ef 100644 --- a/sources/shiboken6/tests/samplebinding/modelindex_test.py +++ b/sources/shiboken6/tests/samplebinding/modelindex_test.py @@ -13,6 +13,7 @@ init_paths() from sample import ModelIndex, ReferentModelIndex, PersistentModelIndex + class TestCastOperator(unittest.TestCase): def testCastOperatorReturningValue(self): @@ -30,4 +31,3 @@ class TestCastOperator(unittest.TestCase): if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/samplebinding/modelview_test.py b/sources/shiboken6/tests/samplebinding/modelview_test.py index 0b5c657d..b5663a04 100644 --- a/sources/shiboken6/tests/samplebinding/modelview_test.py +++ b/sources/shiboken6/tests/samplebinding/modelview_test.py @@ -2,7 +2,8 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -'''Test case for objects that keep references to other object without owning them (e.g. model/view relationships).''' +'''Test case for objects that keep references to other object without owning them + (e.g. model/view relationships).''' import os import sys @@ -17,9 +18,11 @@ from sample import ObjectModel, ObjectType, ObjectView object_name = 'test object' + class MyObject(ObjectType): pass + class ListModelKeepsReference(ObjectModel): def __init__(self, parent=None): ObjectModel.__init__(self, parent) @@ -29,6 +32,7 @@ class ListModelKeepsReference(ObjectModel): def data(self): return self.obj + class ListModelDoesntKeepsReference(ObjectModel): def data(self): obj = MyObject() @@ -55,4 +59,3 @@ class ModelViewTest(unittest.TestCase): if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/samplebinding/modifications_test.py b/sources/shiboken6/tests/samplebinding/modifications_test.py index 00822152..dced1439 100644 --- a/sources/shiboken6/tests/samplebinding/modifications_test.py +++ b/sources/shiboken6/tests/samplebinding/modifications_test.py @@ -16,6 +16,7 @@ init_paths() from sample import Modifications, Point, ByteArray + class ExtModifications(Modifications): def __init__(self): Modifications.__init__(self) @@ -42,12 +43,14 @@ class ModificationsTest(unittest.TestCase): gc.collect() def testRenamedMethodAvailability(self): - '''Test if Modification class really have renamed the 'className' virtual method to 'name'.''' + '''Test if Modification class really have renamed the 'className' + virtual method to 'name'.''' self.assertTrue('className' not in dir(Modifications)) self.assertTrue('name' in dir(Modifications)) def testReimplementationOfRenamedVirtualMethod(self): - '''Test if class inheriting from Modification class have the reimplementation of renamed virtual method called.''' + '''Test if class inheriting from Modification class have the reimplementation + of renamed virtual method called.''' em = ExtModifications() self.assertEqual(self.mods.name(), 'Modifications') self.assertEqual(em.name(), 'ExtModifications') @@ -68,12 +71,14 @@ class ModificationsTest(unittest.TestCase): self.assertEqual(self.mods.doublePlus(7), 14) def testDefaultValueRemoval(self): - '''Test if default value was removed from first argument of Modifications::increment(int).''' + '''Test if default value was removed from first argument of + Modifications::increment(int).''' self.assertRaises(TypeError, self.mods.increment) self.assertEqual(self.mods.increment(7), 8) def testDefaultValueReplacement(self): - '''Test if default values for both arguments of Modifications::power(int, int) were modified.''' + '''Test if default values for both arguments of Modifications::power(int, int) + were modified.''' # original default values: int power(int base = 1, int exponent = 0); self.assertNotEqual(self.mods.power(4), 1) # modified default values: int power(int base = 2, int exponent = 1); @@ -82,12 +87,14 @@ class ModificationsTest(unittest.TestCase): self.assertEqual(self.mods.power(5, 3), 5**3) def testSetNewDefaultValue(self): - '''Test if default value was correctly set to 10 for first argument of Modifications::timesTen(int).''' + '''Test if default value was correctly set to 10 for first argument of + Modifications::timesTen(int).''' self.assertEqual(self.mods.timesTen(7), 70) self.assertEqual(self.mods.timesTen(), 100) def testArgumentRemovalAndReturnTypeModificationWithTypesystemTemplates1(self): - '''Test modifications to method signature and return value using type system templates (case 1).''' + '''Test modifications to method signature and return value using type + system templates (case 1).''' result, ok = self.mods.pointToPair(Point(2, 5)) self.assertEqual(type(ok), bool) self.assertEqual(type(result), tuple) @@ -98,7 +105,8 @@ class ModificationsTest(unittest.TestCase): self.assertEqual(result[1], 5.0) def testArgumentRemovalAndReturnTypeModificationWithTypesystemTemplates2(self): - '''Test modifications to method signature and return value using type system templates (case 2).''' + '''Test modifications to method signature and return value using + type system templates (case 2).''' result, ok = self.mods.multiplyPointCoordsPlusValue(Point(2, 5), 4.1) self.assertEqual(type(ok), bool) self.assertEqual(type(result), float) @@ -112,9 +120,11 @@ class ModificationsTest(unittest.TestCase): self.assertEqual(self.mods.overloaded(1, True, 2), Modifications.Overloaded_ibii) # the others weren't modified self.assertEqual(self.mods.overloaded(1, True, 2, False), Modifications.Overloaded_ibib) - self.assertEqual(self.mods.overloaded(1, False, 2, Point(3, 4)), Modifications.Overloaded_ibiP) + self.assertEqual(self.mods.overloaded(1, False, 2, Point(3, 4)), + Modifications.Overloaded_ibiP) self.assertRaises(TypeError, self.mods.overloaded, 1, True, Point(2, 3), Point(4, 5)) - self.assertEqual(self.mods.over(1, True, Point(2, 3), Point(4, 5)), Modifications.Overloaded_ibPP) + self.assertEqual(self.mods.over(1, True, Point(2, 3), Point(4, 5)), + Modifications.Overloaded_ibPP) def testPointArrayModification(self): points = (Point(1, 1), Point(2, 2)) diff --git a/sources/shiboken6/tests/samplebinding/modified_constructor_test.py b/sources/shiboken6/tests/samplebinding/modified_constructor_test.py index d4b65a88..9791a349 100644 --- a/sources/shiboken6/tests/samplebinding/modified_constructor_test.py +++ b/sources/shiboken6/tests/samplebinding/modified_constructor_test.py @@ -4,7 +4,6 @@ '''Tests cases for ConstructorWithModifiedArgument class.''' -import sys import os import sys import unittest @@ -14,7 +13,7 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from shiboken_paths import init_paths init_paths() -from sample import * +from sample import ModifiedConstructor class ConstructorWithModifiedArgumentTest(unittest.TestCase): @@ -24,6 +23,6 @@ class ConstructorWithModifiedArgumentTest(unittest.TestCase): sampleClass = ModifiedConstructor("10") self.assertTrue(sampleClass.retrieveValue(), 10) + if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/samplebinding/modifiedvirtualmethods_test.py b/sources/shiboken6/tests/samplebinding/modifiedvirtualmethods_test.py index ae090f8e..dcb487f1 100644 --- a/sources/shiboken6/tests/samplebinding/modifiedvirtualmethods_test.py +++ b/sources/shiboken6/tests/samplebinding/modifiedvirtualmethods_test.py @@ -16,6 +16,7 @@ init_paths() from sample import VirtualMethods, Str + class ExtendedVirtualMethods(VirtualMethods): def __init__(self): VirtualMethods.__init__(self) @@ -61,7 +62,7 @@ class ExtendedVirtualMethods(VirtualMethods): self.callMe_called += 1 def getMargins(self): - return tuple([m*2 for m in VirtualMethods.getMargins(self)]) + return tuple([m * 2 for m in VirtualMethods.getMargins(self)]) class VirtualMethodsTest(unittest.TestCase): @@ -185,7 +186,8 @@ class VirtualMethodsTest(unittest.TestCase): removed_arg_value = 2011 default_value = 3000 result = self.evm.callSum4(a0, removed_arg_value, a1) - self.assertEqual(result, (a0 - removed_arg_value + a1 + default_value) * self.evm.multiplier) + self.assertEqual(result, + (a0 - removed_arg_value + a1 + default_value) * self.evm.multiplier) self.assertTrue(self.evm.sum4_called) def testOverridenMethodResultModification(self): @@ -217,15 +219,15 @@ class VirtualMethodsTest(unittest.TestCase): def testExtendedAllArgumentsRemoved(self): values = (10, 20, 30, 40) self.evm.setMargins(*values) - double = tuple([m*2 for m in values]) + double = tuple([m * 2 for m in values]) self.assertEqual(self.evm.getMargins(), double) def testExtendedAllArgumentsRemovedCallVirtual(self): values = (10, 20, 30, 40) self.evm.setMargins(*values) - double = tuple([m*2 for m in values]) + double = tuple([m * 2 for m in values]) self.assertEqual(self.evm.callGetMargins(), double) + if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/samplebinding/multi_cpp_inheritance_test.py b/sources/shiboken6/tests/samplebinding/multi_cpp_inheritance_test.py index 6fd73537..fc6b26c3 100644 --- a/sources/shiboken6/tests/samplebinding/multi_cpp_inheritance_test.py +++ b/sources/shiboken6/tests/samplebinding/multi_cpp_inheritance_test.py @@ -13,77 +13,85 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from shiboken_paths import init_paths init_paths() -from sample import * +from sample import ObjectType, Point, Str + class SimpleUseCase(ObjectType, Str): def __init__(self, name): ObjectType.__init__(self) Str.__init__(self, name) + class SimpleUseCaseReverse(Str, ObjectType): def __init__(self, name): ObjectType.__init__(self) Str.__init__(self, name) + class SimpleUseCase2(SimpleUseCase): def __init__(self, name): SimpleUseCase.__init__(self, name) + class ComplexUseCase(SimpleUseCase2, Point): def __init__(self, name): SimpleUseCase2.__init__(self, name) Point.__init__(self) + class ComplexUseCaseReverse(Point, SimpleUseCase2): def __init__(self, name): SimpleUseCase2.__init__(self, name) Point.__init__(self) + class MultipleCppDerivedTest(unittest.TestCase): - def testInstanciation(self): + def testInstantiation(self): s = SimpleUseCase("Hi") self.assertEqual(s, "Hi") s.setObjectName(s) self.assertEqual(s.objectName(), "Hi") - def testInstanciation2(self): + def testInstantiation2(self): s = SimpleUseCase2("Hi") self.assertEqual(s, "Hi") s.setObjectName(s) self.assertEqual(s.objectName(), "Hi") - def testComplexInstanciation(self): + def testComplexInstantiation(self): c = ComplexUseCase("Hi") self.assertEqual(c, "Hi") c.setObjectName(c) self.assertEqual(c.objectName(), "Hi") - c.setX(2); + c.setX(2) self.assertEqual(c.x(), 2) + class MultipleCppDerivedReverseTest(unittest.TestCase): - def testInstanciation(self): + def testInstantiation(self): s = SimpleUseCaseReverse("Hi") self.assertEqual(s, "Hi") s.setObjectName(s) self.assertEqual(s.objectName(), "Hi") - def testInstanciation2(self): + def testInstantiation2(self): s = SimpleUseCase2("Hi") self.assertEqual(s, "Hi") s.setObjectName(s) self.assertEqual(s.objectName(), "Hi") - def testComplexInstanciation(self): + def testComplexInstantiation(self): # PYSIDE-1564: This test can no longer work because of this MRO: # ('ComplexUseCaseReverse', 'Point', 'SimpleUseCase2', 'SimpleUseCase', # 'ObjectType', 'Str', 'Object', 'object') # By multiple inheritance Point would be called first but has no argument. with self.assertRaises(TypeError): - c = ComplexUseCaseReverse("Hi") + c = ComplexUseCaseReverse("Hi") # noqa: F841 # c.setObjectName(c) # self.assertEqual(c.objectName(), "Hi") # c.setX(2); # self.assertEqual(c, Point(2, 0)) + if __name__ == '__main__': unittest.main() diff --git a/sources/shiboken6/tests/samplebinding/multiple_derived_test.py b/sources/shiboken6/tests/samplebinding/multiple_derived_test.py index 46e44601..7497714a 100644 --- a/sources/shiboken6/tests/samplebinding/multiple_derived_test.py +++ b/sources/shiboken6/tests/samplebinding/multiple_derived_test.py @@ -13,17 +13,20 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from shiboken_paths import init_paths init_paths() -from sample import Base1, Base2, Base3, Base4, Base5, Base6 +from sample import Base1, Base2 from sample import MDerived1, MDerived2, MDerived3, MDerived4, MDerived5, SonOfMDerived1 + class ExtMDerived1(MDerived1): def __init__(self): MDerived1.__init__(self) self.multiplier = 20 self.base2Method_called = False + def base2Method(self): return Base2.base2Method(self) * self.multiplier + class MultipleDerivedTest(unittest.TestCase): '''Test cases for multiple inheritance''' @@ -40,19 +43,22 @@ class MultipleDerivedTest(unittest.TestCase): self.assertTrue(issubclass(MDerived1, Base2)) def testCallToFunctionWithBase1ArgumentThatCastsBackToMDerived1(self): - '''MDerived1 is passed as an Base1 argument to a method that returns it casted back to MDerived1.''' + '''MDerived1 is passed as an Base1 argument to a method that returns + it casted back to MDerived1.''' a = MDerived1() b = MDerived1.transformFromBase1(a) self.assertEqual(a, b) def testCallToFunctionWithBase2ArgumentThatCastsBackToMDerived1(self): - '''MDerived1 is passed as an Base2 argument to a method that returns it casted back to MDerived1.''' + '''MDerived1 is passed as an Base2 argument to a method that returns + it casted back to MDerived1.''' a = MDerived1() b = MDerived1.transformFromBase2(a) self.assertEqual(a, b) def testPythonClassIsInstance(self): - '''Python defined class ExtMDerived1 is instance of its parents MDerived1, Base1 and Base2.''' + '''Python defined class ExtMDerived1 is instance of its parents + MDerived1, Base1 and Base2.''' a = ExtMDerived1() self.assertTrue(isinstance(a, ExtMDerived1)) self.assertTrue(isinstance(a, MDerived1)) @@ -60,14 +66,16 @@ class MultipleDerivedTest(unittest.TestCase): self.assertTrue(isinstance(a, Base2)) def testPythonClassIsSubclass(self): - '''Python defined class ExtMDerived1 is subclass of its parents MDerived1, Base1 and Base2.''' + '''Python defined class ExtMDerived1 is subclass of its parents + MDerived1, Base1 and Base2.''' self.assertTrue(issubclass(ExtMDerived1, MDerived1)) self.assertTrue(issubclass(ExtMDerived1, Base1)) self.assertTrue(issubclass(ExtMDerived1, Base2)) @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount") def testCastFromMDerived1ToBases(self): - '''MDerived1 is casted by C++ to its parents and the binding must return the MDerived1 wrapper.''' + '''MDerived1 is casted by C++ to its parents and the binding must return the + MDerived1 wrapper.''' a = MDerived1() refcnt = sys.getrefcount(a) b1 = a.castToBase1() @@ -80,7 +88,8 @@ class MultipleDerivedTest(unittest.TestCase): @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount") def testCastFromExtMDerived1ToMDerived1Bases(self): - '''Python defined class ExtMDerived1 is casted by C++ to MDerived1 parents and the binding must return the correct ExtMDerived1 instance.''' + '''Python defined class ExtMDerived1 is casted by C++ to MDerived1 parents + and the binding must return the correct ExtMDerived1 instance.''' a = ExtMDerived1() refcnt = sys.getrefcount(a) b1 = a.castToBase1() @@ -95,7 +104,8 @@ class MultipleDerivedTest(unittest.TestCase): @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount") def testCastFromSonOfMDerived1ToBases(self): - '''SonOfMDerived1 is casted by C++ to its parents and the binding must return the SonOfMDerived1 wrapper.''' + '''SonOfMDerived1 is casted by C++ to its parents and the binding must return + the SonOfMDerived1 wrapper.''' a = SonOfMDerived1() refcnt = sys.getrefcount(a) md1 = a.castToMDerived1() @@ -116,7 +126,8 @@ class MultipleDerivedTest(unittest.TestCase): @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount") def testCastFromMDerived2ToBases(self): - '''MDerived2 is casted by C++ to its parents and the binding must return the MDerived2 wrapper.''' + '''MDerived2 is casted by C++ to its parents and the binding must + return the MDerived2 wrapper.''' a = MDerived2() refcnt = sys.getrefcount(a) b3 = a.castToBase3() @@ -135,7 +146,8 @@ class MultipleDerivedTest(unittest.TestCase): @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount") def testCastFromMDerived3ToBases(self): - '''MDerived3 is casted by C++ to its parents and the binding must return the MDerived3 wrapper.''' + '''MDerived3 is casted by C++ to its parents and the binding must + return the MDerived3 wrapper.''' a = MDerived3() refcnt = sys.getrefcount(a) md1 = a.castToMDerived1() @@ -166,7 +178,8 @@ class MultipleDerivedTest(unittest.TestCase): @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount") def testCastFromMDerived4ToBases(self): - '''MDerived4 is casted by C++ to its parents and the binding must return the MDerived4 wrapper.''' + '''MDerived4 is casted by C++ to its parents and the binding must + return the MDerived4 wrapper.''' a = MDerived4() refcnt = sys.getrefcount(a) b3 = a.castToBase3() @@ -179,7 +192,8 @@ class MultipleDerivedTest(unittest.TestCase): @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount") def testCastFromMDerived5ToBases(self): - '''MDerived5 is casted by C++ to its parents and the binding must return the MDerived5 wrapper.''' + '''MDerived5 is casted by C++ to its parents and the binding must + return the MDerived5 wrapper.''' a = MDerived5() refcnt = sys.getrefcount(a) b3 = a.castToBase3() @@ -192,7 +206,8 @@ class MultipleDerivedTest(unittest.TestCase): @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount") def testCastFromMDerived3ToBase3(self): - '''MDerived3 is casted by C++ to Base3 grandparent using both the inherited and reimplement castToBase3 methods.''' + '''MDerived3 is casted by C++ to Base3 grandparent using both the inherited + and reimplement castToBase3 methods.''' a = MDerived3() refcnt = sys.getrefcount(a) b3_reimplemented = a.castToBase3() @@ -203,6 +218,6 @@ class MultipleDerivedTest(unittest.TestCase): self.assertEqual(a, b3_inherited) self.assertEqual(sys.getrefcount(a), refcnt + 2) + if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/samplebinding/namespace_test.py b/sources/shiboken6/tests/samplebinding/namespace_test.py index 9740a137..64a6792a 100644 --- a/sources/shiboken6/tests/samplebinding/namespace_test.py +++ b/sources/shiboken6/tests/samplebinding/namespace_test.py @@ -13,11 +13,9 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from shiboken_paths import init_paths init_paths() -from sample import * +from sample import SampleNamespace from shiboken_test_helper import objectFullname -from shiboken6 import Shiboken - from shibokensupport.signature import get_signature # For tests of invisible namespaces, see @@ -26,17 +24,17 @@ from shibokensupport.signature import get_signature class TestVariablesUnderNamespace(unittest.TestCase): def testIt(self): - self.assertEqual(SampleNamespace.variableInNamespace, 42) + self.assertEqual(SampleNamespace.variableInNamespace, 42) class TestClassesUnderNamespace(unittest.TestCase): def testIt(self): - c1 = SampleNamespace.SomeClass() - e1 = SampleNamespace.SomeClass.ProtectedEnum() - c2 = SampleNamespace.SomeClass.SomeInnerClass() - e2 = SampleNamespace.SomeClass.SomeInnerClass.ProtectedEnum() - c3 = SampleNamespace.SomeClass.SomeInnerClass.OkThisIsRecursiveEnough() - e3 = SampleNamespace.SomeClass.SomeInnerClass.OkThisIsRecursiveEnough.NiceEnum(0) + c1 = SampleNamespace.SomeClass() # noqa F841 + e1 = SampleNamespace.SomeClass.ProtectedEnum() # noqa F841 + c2 = SampleNamespace.SomeClass.SomeInnerClass() # noqa F841 + e2 = SampleNamespace.SomeClass.SomeInnerClass.ProtectedEnum() # noqa F841 + c3 = SampleNamespace.SomeClass.SomeInnerClass.OkThisIsRecursiveEnough() # noqa F841 + e3 = SampleNamespace.SomeClass.SomeInnerClass.OkThisIsRecursiveEnough.NiceEnum(0) # noqa F841 def testFunctionAddedOnNamespace(self): res = SampleNamespace.ImInsideANamespace(2, 2) @@ -44,22 +42,21 @@ class TestClassesUnderNamespace(unittest.TestCase): def testTpNames(self): self.assertEqual(str(SampleNamespace.SomeClass), - "") + "") self.assertEqual(str(SampleNamespace.SomeClass.ProtectedEnum), - "") + "") self.assertEqual(str(SampleNamespace.SomeClass.SomeInnerClass.ProtectedEnum), - "") + "") self.assertEqual(str(SampleNamespace.SomeClass.SomeInnerClass.OkThisIsRecursiveEnough), - "") - self.assertEqual(str(SampleNamespace.SomeClass.SomeInnerClass.OkThisIsRecursiveEnough.NiceEnum), - "") + "") # noqa: E501 + self.assertEqual(str(SampleNamespace.SomeClass.SomeInnerClass.OkThisIsRecursiveEnough.NiceEnum), # noqa: E501 + "") # Test if enum inside of class is correct represented - self.assertEqual(objectFullname(get_signature(SampleNamespace.enumInEnumOut).parameters['in_'].annotation), - "sample.SampleNamespace.InValue") - self.assertEqual(objectFullname(get_signature(SampleNamespace.enumAsInt).parameters['value'].annotation), - "sample.SampleNamespace.SomeClass.PublicScopedEnum") - + an = objectFullname(get_signature(SampleNamespace.enumInEnumOut).parameters['in_'].annotation) # noqa: E501 + self.assertEqual(an, "sample.SampleNamespace.InValue") + an = objectFullname(get_signature(SampleNamespace.enumAsInt).parameters['value'].annotation) + self.assertEqual(an, "sample.SampleNamespace.SomeClass.PublicScopedEnum") def testInlineNamespaces(self): cls = SampleNamespace.ClassWithinInlineNamespace() diff --git a/sources/shiboken6/tests/samplebinding/newdivision_test.py b/sources/shiboken6/tests/samplebinding/newdivision_test.py index 6286dcfc..0e7dfbee 100644 --- a/sources/shiboken6/tests/samplebinding/newdivision_test.py +++ b/sources/shiboken6/tests/samplebinding/newdivision_test.py @@ -10,16 +10,16 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from shiboken_paths import init_paths init_paths() -from sample import * +from sample import Point class TestNewDivision(unittest.TestCase): def testIt(self): p = Point(4, 4) - p2 = p/2 + p2 = p / 2 self.assertEqual(p2, Point(2, 2)) + if __name__ == "__main__": unittest.main() - diff --git a/sources/shiboken6/tests/samplebinding/nondefaultctor_test.py b/sources/shiboken6/tests/samplebinding/nondefaultctor_test.py index 3892508a..bc8d29e5 100644 --- a/sources/shiboken6/tests/samplebinding/nondefaultctor_test.py +++ b/sources/shiboken6/tests/samplebinding/nondefaultctor_test.py @@ -15,14 +15,17 @@ init_paths() from sample import NonDefaultCtor + class DerivedNonDefaultCtor (NonDefaultCtor): def returnMyselfVirtual(self): - return NonDefaultCtor(self.value()+1) + return NonDefaultCtor(self.value() + 1) + class AnotherDerivedNonDefaultCtor (NonDefaultCtor): def __init__(self, some_string): pass + class NonDefaultCtorTest(unittest.TestCase): def testNonDefaultCtor(self): @@ -43,8 +46,8 @@ class NonDefaultCtorTest(unittest.TestCase): self.assertEqual(c.callReturnMyselfVirtual().value(), 4) def testCtorOverload(self): - c = AnotherDerivedNonDefaultCtor("testing") + c = AnotherDerivedNonDefaultCtor("testing") # noqa: F841 + if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/samplebinding/nontypetemplate_test.py b/sources/shiboken6/tests/samplebinding/nontypetemplate_test.py index fa28abc6..a1054772 100644 --- a/sources/shiboken6/tests/samplebinding/nontypetemplate_test.py +++ b/sources/shiboken6/tests/samplebinding/nontypetemplate_test.py @@ -21,6 +21,7 @@ init_paths() from sample import IntArray2, IntArray3 + class NonTypeTemplateTest(unittest.TestCase): def testNonTypeTemplate(self): @@ -32,7 +33,7 @@ class NonTypeTemplateTest(unittest.TestCase): def testArrayInitializer(self): if not hasNumPy: return - array3 = IntArray3(numpy.array([1, 2, 3], dtype = 'int32')) + array3 = IntArray3(numpy.array([1, 2, 3], dtype='int32')) self.assertEqual(array3.sum(), 6) diff --git a/sources/shiboken6/tests/samplebinding/nonzero_test.py b/sources/shiboken6/tests/samplebinding/nonzero_test.py index d2a30c7e..7be239fc 100644 --- a/sources/shiboken6/tests/samplebinding/nonzero_test.py +++ b/sources/shiboken6/tests/samplebinding/nonzero_test.py @@ -13,6 +13,7 @@ init_paths() from sample import Color, Brush + class TestNonZeroOperator(unittest.TestCase): def testColor(self): """Color has a Qt-style isNull()""" diff --git a/sources/shiboken6/tests/samplebinding/numericaltypedef_test.py b/sources/shiboken6/tests/samplebinding/numericaltypedef_test.py index 9539469a..f714a4fc 100644 --- a/sources/shiboken6/tests/samplebinding/numericaltypedef_test.py +++ b/sources/shiboken6/tests/samplebinding/numericaltypedef_test.py @@ -13,6 +13,7 @@ init_paths() from sample import SizeF + class NumericalTypedefTest(unittest.TestCase): def testNumericalTypedefExact(self): @@ -32,5 +33,6 @@ class NumericalTypedefTest(unittest.TestCase): self.assertEqual(SizeF.passTypedefOfUnsignedShort(321), 321) self.assertNotEqual(SizeF.passTypedefOfUnsignedShort(123), 0) + if __name__ == '__main__': unittest.main() diff --git a/sources/shiboken6/tests/samplebinding/numpy_test.py b/sources/shiboken6/tests/samplebinding/numpy_test.py index af1e6d1b..42094a46 100644 --- a/sources/shiboken6/tests/samplebinding/numpy_test.py +++ b/sources/shiboken6/tests/samplebinding/numpy_test.py @@ -8,7 +8,7 @@ try: if bool(sysconfig.get_config_var('Py_DEBUG')): sys.exit(0) import numpy -except: +except: # noqa: E722 sys.exit(0) import os @@ -21,6 +21,7 @@ from shiboken_paths import init_paths init_paths() from sample import PointF + class TestNumpyTypes(unittest.TestCase): def testNumpyConverted(self): @@ -35,6 +36,6 @@ class TestNumpyTypes(unittest.TestCase): self.assertAlmostEqual(p.x(), x) self.assertAlmostEqual(p.y(), y) + if __name__ == "__main__": unittest.main() - diff --git a/sources/shiboken6/tests/samplebinding/objecttype_test.py b/sources/shiboken6/tests/samplebinding/objecttype_test.py index 6e90bfb3..ead68ba1 100644 --- a/sources/shiboken6/tests/samplebinding/objecttype_test.py +++ b/sources/shiboken6/tests/samplebinding/objecttype_test.py @@ -68,7 +68,7 @@ class ObjectTypeTest(unittest.TestCase): def testNextInFocusChainCycle(self): parent = ObjectType() child = ObjectType(parent) - next_focus = child.nextInFocusChain() + next_focus = child.nextInFocusChain() # noqa: F841 Shiboken.invalidate(parent) @@ -105,5 +105,6 @@ class ObjectTypeTest(unittest.TestCase): with self.assertRaises(AttributeError): o.typo + if __name__ == '__main__': unittest.main() diff --git a/sources/shiboken6/tests/samplebinding/objecttype_with_named_args_test.py b/sources/shiboken6/tests/samplebinding/objecttype_with_named_args_test.py index 4e552e88..285e2313 100644 --- a/sources/shiboken6/tests/samplebinding/objecttype_with_named_args_test.py +++ b/sources/shiboken6/tests/samplebinding/objecttype_with_named_args_test.py @@ -13,6 +13,7 @@ init_paths() from sample import ObjectType + class NamedArgsTest(unittest.TestCase): def testOneArgument(self): @@ -35,18 +36,15 @@ class NamedArgsTest(unittest.TestCase): o.setObjectNameWithSize(size=6, name="pyside") self.assertEqual(o.objectName(), "pyside") - def testUseDefaultValues(self): o = ObjectType() o.setObjectNameWithSize(size=3) - self.assertEqual(o.objectName(), "") # user prefix='") # user prefix=' s2) self.assertFalse(s2 > s1) + if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/samplebinding/static_nonstatic_methods_test.py b/sources/shiboken6/tests/samplebinding/static_nonstatic_methods_test.py index e176b334..cf088929 100644 --- a/sources/shiboken6/tests/samplebinding/static_nonstatic_methods_test.py +++ b/sources/shiboken6/tests/samplebinding/static_nonstatic_methods_test.py @@ -15,16 +15,20 @@ init_paths() from sample import SimpleFile + class SimpleFile2 (SimpleFile): def exists(self): return "Mooo" + class SimpleFile3 (SimpleFile): pass + class SimpleFile4 (SimpleFile): exists = 5 + class StaticNonStaticMethodsTest(unittest.TestCase): '''Test cases for overloads involving static and non-static versions of a method.''' @@ -79,9 +83,9 @@ class StaticNonStaticMethodsTest(unittest.TestCase): def testDuckPunchingStaticNonStaticMethod(self): f = SimpleFile(os.fspath(self.existing_filename)) - f.exists = lambda : "Meee" + f.exists = lambda: "Meee" self.assertEqual(f.exists(), "Meee") + if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/samplebinding/str_test.py b/sources/shiboken6/tests/samplebinding/str_test.py index 97df7645..c06fd642 100644 --- a/sources/shiboken6/tests/samplebinding/str_test.py +++ b/sources/shiboken6/tests/samplebinding/str_test.py @@ -2,7 +2,8 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -'''Test cases for a method that receives a reference to class that is implicitly convertible from a Python native type.''' +'''Test cases for a method that receives a reference to class that is implicitly + convertible from a Python native type.''' import os import sys @@ -15,6 +16,7 @@ init_paths() from sample import Str + class StrTest(unittest.TestCase): '''Test cases for thr Str class.''' @@ -31,39 +33,39 @@ class StrTest(unittest.TestCase): self.assertEqual(str(s1), 'This is Sparta!') def testPassPythonTypeImplictlyConvertibleToAClassUsedAsReference(self): - '''Test passing a Python class implicitly convertible to a wrapped class that is expected to be passed as reference.''' + '''Test passing a Python class implicitly convertible to a wrapped class + that is expected to be passed as reference.''' s1 = Str('This is %VAR!').arg('Athens') self.assertEqual(str(s1), 'This is Athens!') def testSequenceOperators(self): s1 = Str("abcdef") - self.assertEqual(len(s1), 6); - self.assertEqual(len(Str()), 0); + self.assertEqual(len(s1), 6) + self.assertEqual(len(Str()), 0) # getitem - self.assertEqual(s1[0], "a"); - self.assertEqual(s1[1], "b"); - self.assertEqual(s1[2], "c"); - self.assertEqual(s1[3], "d"); - self.assertEqual(s1[4], "e"); - self.assertEqual(s1[5], "f"); - self.assertEqual(s1[-1], "f"); - self.assertEqual(s1[-2], "e"); + self.assertEqual(s1[0], "a") + self.assertEqual(s1[1], "b") + self.assertEqual(s1[2], "c") + self.assertEqual(s1[3], "d") + self.assertEqual(s1[4], "e") + self.assertEqual(s1[5], "f") + self.assertEqual(s1[-1], "f") + self.assertEqual(s1[-2], "e") self.assertRaises(TypeError, s1.__getitem__, 6) # setitem s1[0] = 'A' s1[1] = 'B' - self.assertEqual(s1[0], 'A'); - self.assertEqual(s1[1], 'B'); + self.assertEqual(s1[0], 'A') + self.assertEqual(s1[1], 'B') self.assertRaises(TypeError, s1.__setitem__(6, 67)) def testReverseOperator(self): s1 = Str("hello") - n1 = 2 - self.assertEqual(s1+2, "hello2") - self.assertEqual(2+s1, "2hello") + self.assertEqual(s1 + 2, "hello2") + self.assertEqual(2 + s1, "2hello") def testToIntError(self): self.assertEqual(Str('Z').toInt(), (0, False)) @@ -90,6 +92,6 @@ class StrTest(unittest.TestCase): self.assertEqual(val, int(str(hexa), 16)) self.assertEqual(hexa.toInt(), (0, False)) + if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/samplebinding/strlist_test.py b/sources/shiboken6/tests/samplebinding/strlist_test.py index f768f9c6..2bfb80b6 100644 --- a/sources/shiboken6/tests/samplebinding/strlist_test.py +++ b/sources/shiboken6/tests/samplebinding/strlist_test.py @@ -15,6 +15,7 @@ init_paths() from sample import Str, StrList + class StrListTest(unittest.TestCase): '''Test cases for StrList class that inherits from std::list.''' @@ -88,5 +89,6 @@ class StrListTest(unittest.TestCase): self.assertEqual(len(sl), 2) self.assertEqual(sl, (Str('Foo'), 'Bar')) + if __name__ == '__main__': unittest.main() diff --git a/sources/shiboken6/tests/samplebinding/templateinheritingclass_test.py b/sources/shiboken6/tests/samplebinding/templateinheritingclass_test.py index f458ad0d..11279c7e 100644 --- a/sources/shiboken6/tests/samplebinding/templateinheritingclass_test.py +++ b/sources/shiboken6/tests/samplebinding/templateinheritingclass_test.py @@ -14,7 +14,8 @@ init_paths() from sample import Photon '''This tests classes that inherit from template classes, -simulating a situation found in Qt's phonon module.''' + simulating a situation found in Qt's phonon module.''' + class TemplateInheritingClassTest(unittest.TestCase): def testClassBasics(self): @@ -57,5 +58,6 @@ class TemplateInheritingClassTest(unittest.TestCase): self.assertEqual(obj2, obj2.passPointerThrough(obj2)) self.assertRaises(TypeError, obj1.passPointerThrough, obj2) + if __name__ == '__main__': unittest.main() diff --git a/sources/shiboken6/tests/samplebinding/time_test.py b/sources/shiboken6/tests/samplebinding/time_test.py index 9b32aaae..6283a674 100644 --- a/sources/shiboken6/tests/samplebinding/time_test.py +++ b/sources/shiboken6/tests/samplebinding/time_test.py @@ -4,7 +4,6 @@ '''Test cases for constructor and method signature decisor on Time class.''' -import sys import os import sys import unittest @@ -17,6 +16,7 @@ import datetime from sample import Time, ImplicitConv, ObjectType + class TimeTest(unittest.TestCase): '''Test cases for constructor and method signature decisor on Time class. The constructor and one method have these signatures: CTORMETHOD() and @@ -115,6 +115,6 @@ class TimeTest(unittest.TestCase): py = datetime.time(12, 32, 5) self.assertNotEqual(time, py) + if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/samplebinding/transform_test.py b/sources/shiboken6/tests/samplebinding/transform_test.py index a938e6af..7dfd18a4 100644 --- a/sources/shiboken6/tests/samplebinding/transform_test.py +++ b/sources/shiboken6/tests/samplebinding/transform_test.py @@ -15,6 +15,7 @@ init_paths() from sample import Point, applyHomogeneousTransform + class TransformTest(unittest.TestCase): '''Test cases for modifying a function with > 9 arguments.''' @@ -32,5 +33,6 @@ class TransformTest(unittest.TestCase): r = applyHomogeneousTransform(p, 1, 0, 0, 0, 1, 0, 0, 0, 0) self.assertTrue(r is None) + if __name__ == '__main__': unittest.main() diff --git a/sources/shiboken6/tests/samplebinding/typeconverters_test.py b/sources/shiboken6/tests/samplebinding/typeconverters_test.py index 14caf9f4..987ba6df 100644 --- a/sources/shiboken6/tests/samplebinding/typeconverters_test.py +++ b/sources/shiboken6/tests/samplebinding/typeconverters_test.py @@ -15,6 +15,7 @@ init_paths() import sample + class GetPythonTypeByNameTest(unittest.TestCase): '''Uses an added function with inject code that uses the libshiboken @@ -176,6 +177,14 @@ class PrimitiveConversionTest(unittest.TestCase): point.setX(large_int) self.assertEqual(round(point.x()), large_int) + def testUnsignedLongLongAsFloat(self): + """PYSIDE-2652: When passing an unsigned long long to a function taking float, + an unsigned 64bit conversion should be done.""" + point = sample.PointF(1, 2) + large_int = 2**63 + point.setX(large_int) + self.assertEqual(round(point.x()), large_int) + if __name__ == '__main__': unittest.main() diff --git a/sources/shiboken6/tests/samplebinding/typedealloc_test.py b/sources/shiboken6/tests/samplebinding/typedealloc_test.py index 1b47c62b..ce881e80 100644 --- a/sources/shiboken6/tests/samplebinding/typedealloc_test.py +++ b/sources/shiboken6/tests/samplebinding/typedealloc_test.py @@ -33,12 +33,16 @@ class TypeDeallocTest(unittest.TestCase): def testScopeEnd(self): ref = None + def scope(): + class Ext(Point): pass - o = Ext() + + o = Ext() # noqa: F841 global ref ref = weakref.ref(Ext, self.callback) + scope() gc.collect() self.assertTrue(self.called) @@ -46,7 +50,7 @@ class TypeDeallocTest(unittest.TestCase): def testDeleteType(self): class Ext(Point): pass - ref = weakref.ref(Ext, self.callback) + ref = weakref.ref(Ext, self.callback) # noqa: F841 del Ext gc.collect() self.assertTrue(self.called) @@ -54,4 +58,3 @@ class TypeDeallocTest(unittest.TestCase): if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/samplebinding/typedtordoublefree_test.py b/sources/shiboken6/tests/samplebinding/typedtordoublefree_test.py index c486f2bc..ab8e535b 100644 --- a/sources/shiboken6/tests/samplebinding/typedtordoublefree_test.py +++ b/sources/shiboken6/tests/samplebinding/typedtordoublefree_test.py @@ -12,6 +12,7 @@ from shiboken_paths import init_paths init_paths() from sample import ObjectType + class TestTypeDestructorDoubleFree(unittest.TestCase): def testTypeDestructorDoubleFree(self): '''Causes the type destructors of two derived classes to be called.''' @@ -22,13 +23,16 @@ class TestTypeDestructorDoubleFree(unittest.TestCase): obj = ExtObj1() child = ObjectType(parent=obj) self.assertEqual(obj.takeChild(child), child) + class ExtObj2(ObjectType): def __init__(self): ObjectType.__init__(self) + obj = ExtObj2() child = ObjectType(parent=obj) self.assertEqual(obj.takeChild(child), child) scope() + if __name__ == '__main__': unittest.main() diff --git a/sources/shiboken6/tests/samplebinding/typesystem_sample.xml b/sources/shiboken6/tests/samplebinding/typesystem_sample.xml index 40ca5875..e315e599 100644 --- a/sources/shiboken6/tests/samplebinding/typesystem_sample.xml +++ b/sources/shiboken6/tests/samplebinding/typesystem_sample.xml @@ -429,7 +429,7 @@ - + @@ -571,6 +571,14 @@ + + + x = virtualWithOutParameterPyOverride(gil, pyOverride.object()); + return; + + + @@ -633,8 +641,8 @@ // CHECKTYPE and ISCONVERTIBLE are used here for test purposes, don't change them. if (!%CHECKTYPE[ObjectTypeLayout*](layout) && !%ISCONVERTIBLE[ObjectTypeLayout*](layout)) return; - // %CHECKTYPE[ObjectTypeLayout*](layout) - // %ISCONVERTIBLE[ObjectTypeLayout*](layout) + /* %CHECKTYPE[ObjectTypeLayout*](layout) */ + /* %ISCONVERTIBLE[ObjectTypeLayout*](layout) */ ObjectTypeLayout* var; var = %CONVERTTOCPP[ObjectTypeLayout*](layout); // TODO-CONVERTER: erase this @@ -2445,5 +2453,5 @@ - + diff --git a/sources/shiboken6/tests/samplebinding/typesystypedef_test.py b/sources/shiboken6/tests/samplebinding/typesystypedef_test.py index 5e54ef6e..f7f5342e 100644 --- a/sources/shiboken6/tests/samplebinding/typesystypedef_test.py +++ b/sources/shiboken6/tests/samplebinding/typesystypedef_test.py @@ -13,8 +13,7 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from shiboken_paths import init_paths init_paths() -from sample import (ValueWithUnitUser, ValueWithUnitDoubleInch, - ValueWithUnitDoubleMillimeter) +from sample import ValueWithUnitUser, ValueWithUnitDoubleInch class TypeSysTypeDefTest(unittest.TestCase): diff --git a/sources/shiboken6/tests/samplebinding/unsafe_parent_test.py b/sources/shiboken6/tests/samplebinding/unsafe_parent_test.py index dbc4097d..2a7e5cac 100644 --- a/sources/shiboken6/tests/samplebinding/unsafe_parent_test.py +++ b/sources/shiboken6/tests/samplebinding/unsafe_parent_test.py @@ -15,6 +15,7 @@ init_paths() from sample import ObjectType + class DerivedObjectType(ObjectType): def isPython(self): return True @@ -22,12 +23,13 @@ class DerivedObjectType(ObjectType): def createChild(self, parent): return DerivedObjectType(parent) + class ParentTest(unittest.TestCase): def testUunsafeParent(self): o = DerivedObjectType() o.callVirtualCreateChild() + if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/samplebinding/useraddedctor_test.py b/sources/shiboken6/tests/samplebinding/useraddedctor_test.py index 28cbd96f..45d4095b 100644 --- a/sources/shiboken6/tests/samplebinding/useraddedctor_test.py +++ b/sources/shiboken6/tests/samplebinding/useraddedctor_test.py @@ -14,12 +14,13 @@ from shiboken_paths import init_paths init_paths() from sample import Size + class PointTest(unittest.TestCase): def testUsingSelfOnCtor(self): # This is a user added ctor and no errors should happen! s = Size("3x2") self.assertEqual(s.height(), 2) + if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/samplebinding/virtualdtor_test.py b/sources/shiboken6/tests/samplebinding/virtualdtor_test.py index 69c3479c..6be87026 100644 --- a/sources/shiboken6/tests/samplebinding/virtualdtor_test.py +++ b/sources/shiboken6/tests/samplebinding/virtualdtor_test.py @@ -16,10 +16,12 @@ init_paths() from sample import VirtualDtor + class ExtendedVirtualDtor(VirtualDtor): def __init__(self): VirtualDtor.__init__(self) + class VirtualDtorTest(unittest.TestCase): '''Test case for virtual destructor.''' @@ -59,4 +61,3 @@ class VirtualDtorTest(unittest.TestCase): if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/samplebinding/virtualmethods_test.py b/sources/shiboken6/tests/samplebinding/virtualmethods_test.py index 7be424e2..52dc66c9 100644 --- a/sources/shiboken6/tests/samplebinding/virtualmethods_test.py +++ b/sources/shiboken6/tests/samplebinding/virtualmethods_test.py @@ -14,10 +14,11 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from shiboken_paths import init_paths init_paths() -from sample import * +from sample import Point, Str, StrList, VirtualDaughter, VirtualMethods import warnings + class ExtendedVirtualMethods(VirtualMethods): def __init__(self): VirtualMethods.__init__(self) @@ -36,6 +37,7 @@ class ExtendedVirtualMethods(VirtualMethods): # check if recursion is caused by injected code that calls C++. return VirtualMethods.recursionOnModifiedVirtual(self, arg) + 10 + class ExtendedVirtualDaughter(VirtualDaughter): def __init__(self, name): VirtualDaughter.__init__(self, name) @@ -45,6 +47,7 @@ class ExtendedVirtualDaughter(VirtualDaughter): self.grand_daughter_name_called = True return VirtualDaughter.name(self).prepend('Extended') + class ExtendedExtendedVirtualDaughter(ExtendedVirtualDaughter): def __init__(self, name): ExtendedVirtualDaughter.__init__(self, name) @@ -54,6 +57,7 @@ class ExtendedExtendedVirtualDaughter(ExtendedVirtualDaughter): self.grand_grand_daughter_name_called = True return ExtendedVirtualDaughter.name(self).prepend('Extended') + class VirtualMethodsTest(unittest.TestCase): '''Test case for virtual methods''' @@ -66,7 +70,8 @@ class VirtualMethodsTest(unittest.TestCase): gc.collect() def testReimplementedVirtualMethod0(self): - '''Test Python override of a virtual method with various different parameters is correctly called from C++.''' + '''Test Python override of a virtual method with various different parameters + is correctly called from C++.''' vm = VirtualMethods() evm = ExtendedVirtualMethods() pt = Point(1.1, 2.2) @@ -120,6 +125,6 @@ class PrettyErrorMessageTest(unittest.TestCase): obj = ExtendedVirtualMethods() self.assertRaises(RuntimeWarning, obj.callStrListToStdList, StrList()) + if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/samplebinding/visibilitychange_test.py b/sources/shiboken6/tests/samplebinding/visibilitychange_test.py index 1b810e83..becdf742 100644 --- a/sources/shiboken6/tests/samplebinding/visibilitychange_test.py +++ b/sources/shiboken6/tests/samplebinding/visibilitychange_test.py @@ -11,17 +11,17 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from shiboken_paths import init_paths init_paths() -from sample import * +from sample import Base1, MDerived1 + class VisibilityChangeTest(unittest.TestCase): def testVisibilityChange(self): b1 = Base1() - b1.publicMethod() # ok... + b1.publicMethod() # ok... d1 = MDerived1() - self.assertRaises(TypeError, d1.publicMethod); + self.assertRaises(TypeError, d1.publicMethod) + if __name__ == '__main__': unittest.main() - - diff --git a/sources/shiboken6/tests/samplebinding/voidholder_test.py b/sources/shiboken6/tests/samplebinding/voidholder_test.py index 63882cc8..186cb473 100644 --- a/sources/shiboken6/tests/samplebinding/voidholder_test.py +++ b/sources/shiboken6/tests/samplebinding/voidholder_test.py @@ -16,6 +16,7 @@ init_paths() from sample import VoidHolder, Point from shiboken6 import Shiboken + class VoidHolderTest(unittest.TestCase): '''Test case for void pointer manipulation.''' @@ -44,6 +45,6 @@ class VoidHolderTest(unittest.TestCase): voidholder = VoidHolder() self.assertEqual(voidholder.voidPointer(), None) + if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken6/tests/samplebinding/weakref_test.py b/sources/shiboken6/tests/samplebinding/weakref_test.py index 317dfb53..01c6d58d 100644 --- a/sources/shiboken6/tests/samplebinding/weakref_test.py +++ b/sources/shiboken6/tests/samplebinding/weakref_test.py @@ -30,7 +30,7 @@ class WeakrefBasicTest(unittest.TestCase): def testBasic(self): '''ObjectType weakref''' obj = ObjectType() - ref = weakref.ref(obj, self.cb) + ref = weakref.ref(obj, self.cb) # noqa: F841 del obj # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion gc.collect() @@ -39,7 +39,7 @@ class WeakrefBasicTest(unittest.TestCase): def testPrivateDtor(self): '''PrivateDtor weakref''' obj = PrivateDtor.instance() - ref = weakref.ref(obj, self.cb) + ref = weakref.ref(obj, self.cb) # noqa: F841 del obj # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion gc.collect() diff --git a/sources/shiboken6/tests/samplebinding/writableclassdict_test.py b/sources/shiboken6/tests/samplebinding/writableclassdict_test.py index e3ba1008..dfc962db 100644 --- a/sources/shiboken6/tests/samplebinding/writableclassdict_test.py +++ b/sources/shiboken6/tests/samplebinding/writableclassdict_test.py @@ -13,7 +13,10 @@ init_paths() from sample import Point -class ExtPoint(Point): pass + +class ExtPoint(Point): + pass + class TestWritableClassDict(unittest.TestCase): def testSetattrOnClass(self): @@ -29,5 +32,6 @@ class TestWritableClassDict(unittest.TestCase): pt = ExtPoint() self.assertEqual(pt.bar, 321) + if __name__ == '__main__': unittest.main() diff --git a/sources/shiboken6/tests/shibokenmodule/module_test.py b/sources/shiboken6/tests/shibokenmodule/module_test.py index a1f95575..9f9f8f5a 100644 --- a/sources/shiboken6/tests/shibokenmodule/module_test.py +++ b/sources/shiboken6/tests/shibokenmodule/module_test.py @@ -45,6 +45,16 @@ class TestShiboken(unittest.TestCase): self.assertTrue(Shiboken.createdByPython(bb)) bb.disposeObjectType(bb.keepObjectType(obj)) + def testWrapInstancePreserveId(self): + """PYSIDE-31: Verify that wrapInstance() returns the existing wrapper + even if a base class type is specified.""" + v = ObjectView() # inherits ObjectType + addresses = Shiboken.getCppPointer(v) + self.assertTrue(addresses) + address = addresses[0] + wrapped = Shiboken.wrapInstance(address, ObjectType) + self.assertEqual(id(wrapped), id(v)) + def testIsOwnedByPython(self): obj = ObjectType() self.assertTrue(Shiboken.ownedByPython(obj)) diff --git a/sources/shiboken6/tests/smartbinding/CMakeLists.txt b/sources/shiboken6/tests/smartbinding/CMakeLists.txt index 2e729321..c10d7ab6 100644 --- a/sources/shiboken6/tests/smartbinding/CMakeLists.txt +++ b/sources/shiboken6/tests/smartbinding/CMakeLists.txt @@ -20,6 +20,7 @@ ${CMAKE_CURRENT_BINARY_DIR}/smart/stdsharedptrtestbench_wrapper.cpp ${CMAKE_CURRENT_BINARY_DIR}/smart/stdsharedptrvirtualmethodtester_wrapper.cpp ${CMAKE_CURRENT_BINARY_DIR}/smart/std_shared_ptr_integer_wrapper.cpp ${CMAKE_CURRENT_BINARY_DIR}/smart/std_shared_ptr_int_wrapper.cpp +${CMAKE_CURRENT_BINARY_DIR}/smart/std_shared_ptr_std_string_wrapper.cpp ${CMAKE_CURRENT_BINARY_DIR}/smart/std_wrapper.cpp ${CMAKE_CURRENT_BINARY_DIR}/smart/std_optional_int_wrapper.cpp ${CMAKE_CURRENT_BINARY_DIR}/smart/std_optional_integer_wrapper.cpp diff --git a/sources/shiboken6/tests/smartbinding/smart_pointer_test.py b/sources/shiboken6/tests/smartbinding/smart_pointer_test.py index 1bec3a28..8d427255 100644 --- a/sources/shiboken6/tests/smartbinding/smart_pointer_test.py +++ b/sources/shiboken6/tests/smartbinding/smart_pointer_test.py @@ -262,7 +262,7 @@ class SmartPointerTests(unittest.TestCase): self.assertTrue(five > four) self.assertRaises(NotImplementedError, - lambda : Obj.createNullSharedPtrInteger() == four) + lambda: Obj.createNullSharedPtrInteger() == four) def testSmartPointerObjectComparison(self): """Test a pointee class without comparison operators.""" diff --git a/sources/shiboken6/tests/smartbinding/std_optional_test.py b/sources/shiboken6/tests/smartbinding/std_optional_test.py index be969d0f..bee57354 100644 --- a/sources/shiboken6/tests/smartbinding/std_optional_test.py +++ b/sources/shiboken6/tests/smartbinding/std_optional_test.py @@ -2,7 +2,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -import gc import os import sys import unittest diff --git a/sources/shiboken6/tests/smartbinding/std_shared_ptr_test.py b/sources/shiboken6/tests/smartbinding/std_shared_ptr_test.py index 1933b533..ddaab43f 100644 --- a/sources/shiboken6/tests/smartbinding/std_shared_ptr_test.py +++ b/sources/shiboken6/tests/smartbinding/std_shared_ptr_test.py @@ -2,7 +2,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -import gc import os import sys import unittest @@ -50,6 +49,15 @@ class StdSharedPtrTests(unittest.TestCase): self.assertFalse(np) p = StdSharedPtrTestBench.createInt() StdSharedPtrTestBench.printInt(p) + ip = std.StdIntPtr(42) + StdSharedPtrTestBench.printInt(ip) + + def testString(self): + np = StdSharedPtrTestBench.createNullString() + StdSharedPtrTestBench.printString(np) + self.assertFalse(np) + p = StdSharedPtrTestBench.createString("bla") + StdSharedPtrTestBench.printString(p) def testVirtuals(self): """Test whether code generating virtual function overrides is generated diff --git a/sources/shiboken6/tests/smartbinding/std_unique_ptr_test.py b/sources/shiboken6/tests/smartbinding/std_unique_ptr_test.py index 0f472941..9c7ef2f0 100644 --- a/sources/shiboken6/tests/smartbinding/std_unique_ptr_test.py +++ b/sources/shiboken6/tests/smartbinding/std_unique_ptr_test.py @@ -2,7 +2,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -import gc import os import sys import unittest diff --git a/sources/shiboken6/tests/smartbinding/typesystem_smart.xml b/sources/shiboken6/tests/smartbinding/typesystem_smart.xml index 261d5f15..e4a1ef8b 100644 --- a/sources/shiboken6/tests/smartbinding/typesystem_smart.xml +++ b/sources/shiboken6/tests/smartbinding/typesystem_smart.xml @@ -50,7 +50,7 @@ value-check-method="operator bool" ref-count-method="use_count" reset-method="reset" - instantiations="Integer,int"> + instantiations="Integer,int=StdIntPtr,std::string"> diff --git a/testing/wheel_tester.py b/testing/wheel_tester.py index 24bc890b..b36ee55a 100644 --- a/testing/wheel_tester.py +++ b/testing/wheel_tester.py @@ -229,7 +229,12 @@ def _run_deploy_test(example, tmpdirname): raise RuntimeError("Error deploying") suffix = "exe" if sys.platform == "win32" else "bin" - binary = f"{tmpdirname}/{main_file.stem}.{suffix}" + + if sys.platform != "darwin": + binary = f"{tmpdirname}/{main_file.stem}.{suffix}" + else: + binary = f"{tmpdirname}/pyside_app_demo.app/Contents/MacOS/{main_file.stem}" + if run_process([binary]) != 0: raise RuntimeError("Error running the deployed example") return True diff --git a/tools/create_changelog.py b/tools/create_changelog.py index 96910ebd..6c24f417 100644 --- a/tools/create_changelog.py +++ b/tools/create_changelog.py @@ -50,7 +50,7 @@ def change_log(version: list) -> Path: def is_lts_version(version: list) -> bool: - return version[0] == 5 or version[1] == 2 + return version[0] == 5 or version[1] in (2, 5) def version_tag(version: list) -> str: @@ -126,7 +126,7 @@ def parse_options() -> Namespace: # For major/minor releases, skip all fixes with "Pick-to: " since they # appear in bug-fix releases. if args.type != "bug-fix": - args.exclude = True + args.exclude = True print(f'Assuming "{args.type}" version', file=sys.stderr) if args.type not in ("bug-fix", "minor", "major"): diff --git a/tools/cross_compile_android/android_utilities.py b/tools/cross_compile_android/android_utilities.py index b09ab7bd..039fa943 100644 --- a/tools/cross_compile_android/android_utilities.py +++ b/tools/cross_compile_android/android_utilities.py @@ -16,7 +16,7 @@ from tqdm import tqdm # the tag number does not matter much since we update the sdk later DEFAULT_SDK_TAG = 6514223 -ANDROID_NDK_VERSION = "25c" +ANDROID_NDK_VERSION = "26b" def run_command(command: List[str], cwd: str = None, ignore_fail: bool = False, diff --git a/tools/cross_compile_android/main.py b/tools/cross_compile_android/main.py index 9c4616e9..bda438cc 100644 --- a/tools/cross_compile_android/main.py +++ b/tools/cross_compile_android/main.py @@ -18,7 +18,7 @@ from android_utilities import (run_command, download_android_commandlinetools, download_android_ndk, install_android_packages) # Note: Does not work with PyEnv. Your Host Python should contain openssl. -PYTHON_VERSION = "3.10" +PYTHON_VERSION = "3.11" SKIP_UPDATE_HELP = ("skip the updation of SDK packages build-tools, platform-tools to" " latest version") @@ -86,7 +86,7 @@ if __name__ == "__main__": parser.add_argument("-v", "--verbose", help="run in verbose mode", action="store_const", dest="loglevel", const=logging.INFO) - parser.add_argument("--api-level", type=str, default="31", help="Android API level to use") + parser.add_argument("--api-level", type=str, default="33", help="Android API level to use") parser.add_argument("--ndk-path", type=str, help="Path to Android NDK (Preferred r25c)") # sdk path is needed to compile all the Qt Java Acitivity files into Qt6AndroidBindings.jar parser.add_argument("--sdk-path", type=str, help="Path to Android SDK") @@ -221,6 +221,7 @@ if __name__ == "__main__": ndk_path=ndk_path, api_level=platform_data.api_level, android_py_install_path_prefix=pyside6_deploy_cache, + host_python_path=sys.executable ) logging.info(f"Writing Python cross compile script into {python_ccompile_script}") @@ -288,12 +289,12 @@ if __name__ == "__main__": # run the cross compile script logging.info(f"Running Qt for Python cross-compile for platform {platform_data.plat_name}") qfp_ccompile_cmd = [sys.executable, "setup.py", "bdist_wheel", "--parallel=9", - "--standalone", "--limited-api=yes", + "--standalone", f"--cmake-toolchain-file={str(qfp_toolchain.resolve())}", f"--qt-host-path={qt_install_path}/gcc_64", f"--plat-name=android_{platform_data.plat_name}", f"--python-target-path={python_path}", (f"--qt-target-path={qt_install_path}/" f"android_{platform_data.qt_plat_name}"), - "--no-qt-tools", "--unity"] + "--no-qt-tools"] run_command(qfp_ccompile_cmd, cwd=pyside_setup_dir, dry_run=dry_run, show_stdout=True) diff --git a/tools/cross_compile_android/templates/cross_compile.tmpl.sh b/tools/cross_compile_android/templates/cross_compile.tmpl.sh index 85d032e2..a6890759 100644 --- a/tools/cross_compile_android/templates/cross_compile.tmpl.sh +++ b/tools/cross_compile_android/templates/cross_compile.tmpl.sh @@ -20,7 +20,8 @@ export RANLIB=$TOOLCHAIN/llvm-ranlib export LD=$TOOLCHAIN/ld export READELF=$TOOLCHAIN/llvm-readelf export CFLAGS='-fPIC -DANDROID' -./configure --host=$HOST_ARCH --target=$HOST_ARCH --build=x86_64-pc-linux-gnu --enable-shared \ +./configure --host=$HOST_ARCH --target=$HOST_ARCH --build=x86_64-pc-linux-gnu \ +--with-build-python={{ host_python_path }} --enable-shared \ --enable-ipv6 ac_cv_file__dev_ptmx=yes ac_cv_file__dev_ptc=no --without-ensurepip \ ac_cv_little_endian_double=yes make BLDSHARED="$CC -shared" CROSS-COMPILE=$TOOL_PREFIX- CROSS_COMPILE_TARGET=yes diff --git a/tools/cross_compile_android/templates/toolchain_default.tmpl.cmake b/tools/cross_compile_android/templates/toolchain_default.tmpl.cmake index a691d50f..3c9752f4 100644 --- a/tools/cross_compile_android/templates/toolchain_default.tmpl.cmake +++ b/tools/cross_compile_android/templates/toolchain_default.tmpl.cmake @@ -2,7 +2,7 @@ # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only # toolchain file to cross compile Qt for Python wheels for Android -cmake_minimum_required(VERSION 3.18) +cmake_minimum_required(VERSION 3.23) include_guard(GLOBAL) set(CMAKE_SYSTEM_NAME Android) {% if plat_name == "armv7a" -%} diff --git a/tools/example_gallery/main.py b/tools/example_gallery/main.py index 40faff13..b5aa632c 100644 --- a/tools/example_gallery/main.py +++ b/tools/example_gallery/main.py @@ -20,7 +20,7 @@ import zipfile import sys from argparse import ArgumentParser, RawTextHelpFormatter from dataclasses import dataclass -from enum import Enum +from enum import IntEnum, Enum from pathlib import Path from textwrap import dedent @@ -30,6 +30,12 @@ class Format(Enum): MD = 1 +class ModuleType(IntEnum): + ESSENTIALS = 0 + ADDONS = 1 + M2M = 2 + + SUFFIXES = {Format.RST: "rst", Format.MD: "md"} @@ -100,6 +106,87 @@ def check_img_ext(i): return i.suffix in IMAGE_SUFFIXES +@dataclass +class ModuleDescription: + """Specifies a sort key and type for a Qt module.""" + sort_key: int = 0 + module_type: ModuleType = ModuleType.ESSENTIALS + description: str = '' + + +MODULE_DESCRIPTIONS = { + "async": ModuleDescription(16, ModuleType.ESSENTIALS, ''), + "corelib": ModuleDescription(15, ModuleType.ESSENTIALS, ''), + "dbus": ModuleDescription(22, ModuleType.ESSENTIALS, ''), + "designer": ModuleDescription(11, ModuleType.ESSENTIALS, ''), + "gui": ModuleDescription(25, ModuleType.ESSENTIALS, ''), + "network": ModuleDescription(20, ModuleType.ESSENTIALS, ''), + "opengl": ModuleDescription(26, ModuleType.ESSENTIALS, ''), + "qml": ModuleDescription(0, ModuleType.ESSENTIALS, ''), + "quick": ModuleDescription(1, ModuleType.ESSENTIALS, ''), + "quickcontrols": ModuleDescription(2, ModuleType.ESSENTIALS, ''), + "samplebinding": ModuleDescription(30, ModuleType.ESSENTIALS, ''), + "scriptableapplication": ModuleDescription(30, ModuleType.ESSENTIALS, ''), + "sql": ModuleDescription(21, ModuleType.ESSENTIALS, ''), + "uitools": ModuleDescription(12, ModuleType.ESSENTIALS, ''), + "widgetbinding": ModuleDescription(30, ModuleType.ESSENTIALS, ''), + "widgets": ModuleDescription(10, ModuleType.ESSENTIALS, ''), + "xml": ModuleDescription(24, ModuleType.ESSENTIALS, ''), + "Qt Demos": ModuleDescription(0, ModuleType.ADDONS, ''), # from Qt repos + "3d": ModuleDescription(30, ModuleType.ADDONS, ''), + "axcontainer": ModuleDescription(20, ModuleType.ADDONS, ''), + "bluetooth": ModuleDescription(20, ModuleType.ADDONS, ''), + "charts": ModuleDescription(12, ModuleType.ADDONS, ''), + "datavisualization": ModuleDescription(11, ModuleType.ADDONS, ''), + "demos": ModuleDescription(0, ModuleType.ADDONS, ''), + "external": ModuleDescription(20, ModuleType.ADDONS, ''), + "graphs": ModuleDescription(10, ModuleType.ADDONS, ''), + "httpserver": ModuleDescription(0, ModuleType.ADDONS, ''), + "location": ModuleDescription(20, ModuleType.ADDONS, ''), + "multimedia": ModuleDescription(12, ModuleType.ADDONS, ''), + "networkauth": ModuleDescription(20, ModuleType.ADDONS, ''), + "pdf": ModuleDescription(20, ModuleType.ADDONS, ''), + "pdfwidgets": ModuleDescription(20, ModuleType.ADDONS, ''), + "quick3d": ModuleDescription(20, ModuleType.ADDONS, ''), + "remoteobjects": ModuleDescription(20, ModuleType.ADDONS, ''), + "serialbus": ModuleDescription(30, ModuleType.ADDONS, ''), + "serialport": ModuleDescription(30, ModuleType.ADDONS, ''), + "spatialaudio": ModuleDescription(20, ModuleType.ADDONS, ''), + "speech": ModuleDescription(20, ModuleType.ADDONS, ''), + "statemachine": ModuleDescription(30, ModuleType.ADDONS, ''), + "webchannel": ModuleDescription(30, ModuleType.ADDONS, ''), + "webenginequick": ModuleDescription(15, ModuleType.ADDONS, ''), + "webenginewidgets": ModuleDescription(16, ModuleType.ADDONS, ''), + "coap": ModuleDescription(0, ModuleType.M2M, ''), + "mqtt": ModuleDescription(0, ModuleType.M2M, ''), + "opcua": ModuleDescription(0, ModuleType.M2M, '') +} + + +def module_sort_key(name): + """Return key for sorting modules.""" + description = MODULE_DESCRIPTIONS.get(name) + module_type = int(description.module_type) if description else 5 + sort_key = description.sort_key if description else 100 + return f"{module_type}:{sort_key:04}:{name}" + + +def module_title(name): + """Return title for a module.""" + result = name.title() + description = MODULE_DESCRIPTIONS.get(name) + if description: + if description.description: + result += " - " + description.description + if description.module_type == ModuleType.M2M: + result += " (M2M)" + elif description.module_type == ModuleType.ADDONS: + result += " (Add-ons)" + else: + result += " (Essentials)" + return result + + @dataclass class ExampleData: """Example data for formatting the gallery.""" @@ -498,10 +585,15 @@ def write_example(example_root, pyproject_file, pyside_example=True): return (p.module_name, result) +def example_sort_key(example: ExampleData): + name = example.example + return "AAA" + name if "gallery" in name else name + + def sort_examples(example): result = {} for module in example.keys(): - result[module] = sorted(example.get(module), key=lambda e: e.doc_file) + result[module] = sorted(example.get(module), key=example_sort_key) return result @@ -548,7 +640,7 @@ if __name__ == "__main__": shutil.rmtree(EXAMPLES_DOC, ignore_errors=True) if not opt_quiet: print("WARNING: Deleted old html directory") - EXAMPLES_DOC.mkdir() + EXAMPLES_DOC.mkdir(exist_ok=True) scan_examples_dir(EXAMPLES_DIR) if options.qt_src_dir: @@ -578,11 +670,13 @@ if __name__ == "__main__": index_files = [] with open(f"{EXAMPLES_DOC}/index.rst", "w") as f: f.write(BASE_CONTENT) - for module_name, e in sorted(examples.items()): + for module_name in sorted(examples.keys(), key=module_sort_key): + e = examples.get(module_name) for i in e: index_files.append(i.doc_file) - f.write(f"{module_name.title()}\n") - f.write(f"{'*' * len(module_name.title())}\n") + title = module_title(module_name) + f.write(f"{title}\n") + f.write(f"{'*' * len(title)}\n") f.write(get_module_gallery(e)) f.write("\n\n") f.write(footer_index) diff --git a/tools/snippets_translate/module_classes.py b/tools/snippets_translate/module_classes.py index 4992e170..df4c7557 100644 --- a/tools/snippets_translate/module_classes.py +++ b/tools/snippets_translate/module_classes.py @@ -532,6 +532,7 @@ module_classes = { "QAccessibleEvent", "QAccessibleInterface", "QAccessibleObject", + "QAccessibleSelectionInterface", "QAccessibleStateChangeEvent", "QAccessibleTableCellInterface", "QAccessibleTableModelChangeEvent", diff --git a/wheel_artifacts/pyproject.toml.base b/wheel_artifacts/pyproject.toml.base index 9e8546e8..2e30e9ae 100644 --- a/wheel_artifacts/pyproject.toml.base +++ b/wheel_artifacts/pyproject.toml.base @@ -10,7 +10,7 @@ authors = [ description = PROJECT_DESCRIPTION readme = PROJECT_README dynamic = ["version"] -requires-python = ">=3.8, <3.13" +requires-python = ">=3.9, <3.13" keywords = ["Qt"] license = {text = "LGPL"} dependencies = PROJECT_DEPENDENCIES @@ -31,7 +31,6 @@ classifiers = [ "Programming Language :: C++", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", @@ -54,7 +53,7 @@ Tracker = "https://bugreports.qt.io/projects/PYSIDE" PROJECT_SCRIPTS [tool.distutils.bdist_wheel] -py_limited_api = "cp38" +py_limited_api = "cp39" plat_name = PROJECT_TAG [tool.setuptools.dynamic]